Qt 6.x
The Qt SDK
Loading...
Searching...
No Matches
qtimezoneprivate.cpp
Go to the documentation of this file.
1// Copyright (C) 2022 The Qt Company Ltd.
2// Copyright (C) 2013 John Layt <jlayt@kde.org>
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
4
5
6#include "qtimezone.h"
9
10#include <private/qnumeric_p.h>
11#include <private/qtools_p.h>
12#include <qdatastream.h>
13#include <qdebug.h>
14
15#include <algorithm>
16
18
19using namespace QtMiscUtils;
20
21/*
22 Static utilities for looking up Windows ID tables
23*/
24
25static quint16 toWindowsIdKey(const QByteArray &winId)
26{
27 for (const QWindowsData &data : windowsDataTable) {
28 if (data.windowsId() == winId)
29 return data.windowsIdKey;
30 }
31 return 0;
32}
33
35{
36 for (const QWindowsData &data : windowsDataTable) {
37 if (data.windowsIdKey == windowsIdKey)
38 return data.windowsId().toByteArray();
39 }
40 return QByteArray();
41}
42
43/*
44 Base class implementing common utility routines, only instantiate for a null tz.
45*/
46
48{
49}
50
52 : QSharedData(other), m_id(other.m_id)
53{
54}
55
57{
58}
59
61{
62 return new QTimeZonePrivate(*this);
63}
64
66{
67 // TODO Too simple, but need to solve problem of comparing different derived classes
68 // Should work for all System and ICU classes as names guaranteed unique, but not for Simple.
69 // Perhaps once all classes have working transitions can compare full list?
70 return (m_id == other.m_id);
71}
72
74{
75 return !(*this == other);
76}
77
79{
80 return !m_id.isEmpty();
81}
82
84{
85 return m_id;
86}
87
89{
90 // Default fall-back mode, use the zoneTable to find Region of known Zones
91 const QLatin1StringView sought(m_id.data(), m_id.size());
92 for (const QZoneData &data : zoneDataTable) {
93 for (QLatin1StringView token : data.ids()) {
94 if (token == sought)
95 return QLocale::Territory(data.territory);
96 }
97 }
99}
100
102{
103 return QString();
104}
105
107 QTimeZone::NameType nameType,
108 const QLocale &locale) const
109{
110 if (nameType == QTimeZone::OffsetName)
111 return isoOffsetFormat(offsetFromUtc(atMSecsSinceEpoch));
112
113 if (isDaylightTime(atMSecsSinceEpoch))
114 return displayName(QTimeZone::DaylightTime, nameType, locale);
115 else
116 return displayName(QTimeZone::StandardTime, nameType, locale);
117}
118
119QString QTimeZonePrivate::displayName(QTimeZone::TimeType timeType,
120 QTimeZone::NameType nameType,
121 const QLocale &locale) const
122{
123 Q_UNUSED(timeType);
124 Q_UNUSED(nameType);
125 Q_UNUSED(locale);
126 return QString();
127}
128
130{
131 Q_UNUSED(atMSecsSinceEpoch);
132 return QString();
133}
134
135int QTimeZonePrivate::offsetFromUtc(qint64 atMSecsSinceEpoch) const
136{
137 const int std = standardTimeOffset(atMSecsSinceEpoch);
138 const int dst = daylightTimeOffset(atMSecsSinceEpoch);
139 const int bad = invalidSeconds();
140 return std == bad || dst == bad ? bad : std + dst;
141}
142
144{
145 Q_UNUSED(atMSecsSinceEpoch);
146 return invalidSeconds();
147}
148
150{
151 Q_UNUSED(atMSecsSinceEpoch);
152 return invalidSeconds();
153}
154
156{
157 return false;
158}
159
160bool QTimeZonePrivate::isDaylightTime(qint64 atMSecsSinceEpoch) const
161{
162 Q_UNUSED(atMSecsSinceEpoch);
163 return false;
164}
165
167{
168 Q_UNUSED(forMSecsSinceEpoch);
169 return invalidData();
170}
171
172// Private only method for use by QDateTime to convert local msecs to epoch msecs
174{
175#ifndef Q_OS_ANDROID
176 // The Android back-end's hasDaylightTime() is only true for zones with
177 // transitions in the future; we need it to mean "has ever had a transition"
178 // though, so can't trust it here.
179 if (!hasDaylightTime()) // No DST means same offset for all local msecs
180 return data(forLocalMSecs - standardTimeOffset(forLocalMSecs) * 1000);
181#endif
182
183 /*
184 We need a UTC time at which to ask for the offset, in order to be able to
185 add that offset to forLocalMSecs, to get the UTC time we need.
186 Fortunately, all time-zone offsets have been less than 17 hours; and DST
187 transitions happen (much) more than thirty-four hours apart. So sampling
188 offset seventeen hours each side gives us information we can be sure
189 brackets the correct time and at most one DST transition.
190 */
191 std::integral_constant<qint64, 17 * 3600 * 1000> seventeenHoursInMSecs;
192 static_assert(-seventeenHoursInMSecs / 1000 < QTimeZone::MinUtcOffsetSecs
193 && seventeenHoursInMSecs / 1000 > QTimeZone::MaxUtcOffsetSecs);
194 qint64 millis;
195 // Clip the bracketing times to the bounds of the supported range. Exclude
196 // minMSecs(), because at least one backend (Windows) uses it for a
197 // start-of-time fake transition, that we want previousTransition() to find.
198 const qint64 recent =
199 qSubOverflow(forLocalMSecs, seventeenHoursInMSecs, &millis) || millis <= minMSecs()
200 ? minMSecs() + 1 : millis; // Necessarily <= forLocalMSecs + 2.
201 // (Given that minMSecs() is std::numeric_limits<qint64>::min() + 1.)
202 const qint64 imminent =
203 qAddOverflow(forLocalMSecs, seventeenHoursInMSecs, &millis)
204 ? maxMSecs() : millis; // Necessarily >= forLocalMSecs
205 // At most one of those was clipped to its boundary value:
206 Q_ASSERT(recent < imminent && seventeenHoursInMSecs < imminent - recent + 2);
207 /*
208 Offsets are Local - UTC, positive to the east of Greenwich, negative to
209 the west; DST offset always exceeds standard offset, when DST applies.
210 When we have offsets on either side of a transition, the lower one is
211 standard, the higher is DST.
212
213 Non-DST transitions (jurisdictions changing time-zone and time-zones
214 changing their standard offset, typically) are described below as if they
215 were DST transitions (since these are more usual and familiar); the code
216 mostly concerns itself with offsets from UTC, described in terms of the
217 common case for changes in that. If there is no actual change in offset
218 (e.g. a DST transition cancelled by a standard offset change), this code
219 should handle it gracefully; without transitions, it'll see early == late
220 and take the easy path; with transitions, tran and nextTran get the
221 correct UTC time as atMSecsSinceEpoch so comparing to nextStart selects
222 the right one. In all other cases, the transition changes offset and the
223 reasoning that applies to DST applies just the same. Aside from hinting,
224 the only thing that looks at DST-ness at all, other than inferred from
225 offset changes, is the case without transition data handling an invalid
226 time in the gap that a transition passed over.
227
228 The handling of hint (see below) is apt to go wrong in non-DST
229 transitions. There isn't really a great deal we can hope to do about that
230 without adding yet more unreliable complexity to the heuristics in use for
231 already obscure corner-cases.
232 */
233
234 /*
235 The hint (really a QDateTimePrivate::DaylightStatus) is > 0 if caller
236 thinks we're in DST, 0 if in standard. A value of -2 means never-DST, so
237 should have been handled above; if it slips through, it's wrong but we
238 should probably treat it as standard anyway (never-DST means
239 always-standard, after all). If the hint turns out to be wrong, fall back
240 on trying the other possibility: which makes it harmless to treat -1
241 (meaning unknown) as standard (i.e. try standard first, then try DST). In
242 practice, away from a transition, the only difference hint makes is to
243 which candidate we try first: if the hint is wrong (or unknown and
244 standard fails), we'll try the other candidate and it'll work.
245
246 For the obscure (and invalid) case where forLocalMSecs falls in a
247 spring-forward's missing hour, a common case is that we started with a
248 date/time for which the hint was valid and adjusted it naively; for that
249 case, we should correct the adjustment by shunting across the transition
250 into where hint is wrong. So half-way through the gap, arrived at from
251 the DST side, should be read as an hour earlier, in standard time; but, if
252 arrived at from the standard side, should be read as an hour later, in
253 DST. (This shall be wrong in some cases; for example, when a country
254 changes its transition dates and changing a date/time by more than six
255 months lands it on a transition. However, these cases are even more
256 obscure than those where the heuristic is good.)
257 */
258
259 if (hasTransitions()) {
260 /*
261 We have transitions.
262
263 Each transition gives the offsets to use until the next; so we need the
264 most recent transition before the time forLocalMSecs describes. If it
265 describes a time *in* a transition, we'll need both that transition and
266 the one before it. So find one transition that's probably after (and not
267 much before, otherwise) and another that's definitely before, then work
268 out which one to use. When both or neither work on forLocalMSecs, use
269 hint to disambiguate.
270 */
271
272 // Get a transition definitely before the local MSecs; usually all we need.
273 // Only around the transition times might we need another.
274 Data tran = previousTransition(recent);
275 Q_ASSERT(forLocalMSecs < 0 || // Pre-epoch TZ info may be unavailable
276 forLocalMSecs - tran.offsetFromUtc * 1000 >= tran.atMSecsSinceEpoch);
277 Data nextTran = nextTransition(tran.atMSecsSinceEpoch);
278 /*
279 Now walk those forward until they bracket forLocalMSecs with transitions.
280
281 One of the transitions should then be telling us the right offset to use.
282 In a transition, we need the transition before it (to describe the run-up
283 to the transition) and the transition itself; so we need to stop when
284 nextTran is that transition.
285 */
286 while (nextTran.atMSecsSinceEpoch != invalidMSecs()
287 && forLocalMSecs > nextTran.atMSecsSinceEpoch + nextTran.offsetFromUtc * 1000) {
288 Data newTran = nextTransition(nextTran.atMSecsSinceEpoch);
289 if (newTran.atMSecsSinceEpoch == invalidMSecs()
290 || newTran.atMSecsSinceEpoch + newTran.offsetFromUtc * 1000 > imminent) {
291 // Definitely not a relevant tansition: too far in the future.
292 break;
293 }
294 tran = nextTran;
295 nextTran = newTran;
296 }
297
298 // Check we do *really* have transitions for this zone:
299 if (tran.atMSecsSinceEpoch != invalidMSecs()) {
300 /* So now tran is definitely before ... */
301 Q_ASSERT(forLocalMSecs < 0
302 || forLocalMSecs - tran.offsetFromUtc * 1000 > tran.atMSecsSinceEpoch);
303 // Work out the UTC value it would make sense to return if using tran:
304 tran.atMSecsSinceEpoch = forLocalMSecs - tran.offsetFromUtc * 1000;
305 // If we know of no transition after it, the answer is easy:
306 const qint64 nextStart = nextTran.atMSecsSinceEpoch;
307 if (nextStart == invalidMSecs())
308 return tran;
309
310 /*
311 ... and nextTran is either after or only slightly before. We're
312 going to interpret one as standard time, the other as DST
313 (although the transition might in fact be a change in standard
314 offset, or a change in DST offset, e.g. to/from double-DST). Our
315 hint tells us which of those to use (defaulting to standard if no
316 hint): try it first; if that fails, try the other; if both fail,
317 life's tricky.
318 */
319 // Work out the UTC value it would make sense to return if using nextTran:
320 nextTran.atMSecsSinceEpoch = forLocalMSecs - nextTran.offsetFromUtc * 1000;
321
322 // If both or neither have zero DST, treat the one with lower offset as standard:
323 const bool nextIsDst = !nextTran.daylightTimeOffset == !tran.daylightTimeOffset
324 ? tran.offsetFromUtc < nextTran.offsetFromUtc : nextTran.daylightTimeOffset;
325 // If that agrees with hint > 0, our first guess is to use nextTran; else tran.
326 const bool nextFirst = nextIsDst == (hint > 0);
327 for (int i = 0; i < 2; i++) {
328 /*
329 On the first pass, the case we consider is what hint told us to expect
330 (except when hint was -1 and didn't actually tell us what to expect),
331 so it's likely right. We only get a second pass if the first failed,
332 by which time the second case, that we're trying, is likely right.
333 */
334 if (nextFirst ? i == 0 : i) {
335 if (nextStart <= nextTran.atMSecsSinceEpoch)
336 return nextTran;
337 } else {
338 // If next is invalid, nextFirst is false, to route us here first:
339 if (nextStart > tran.atMSecsSinceEpoch)
340 return tran;
341 }
342 }
343
344 /*
345 Neither is valid (e.g. in a spring-forward's gap) and
346 nextTran.atMSecsSinceEpoch < nextStart <= tran.atMSecsSinceEpoch;
347 swap their atMSecsSinceEpoch to give each a moment on its side of
348 the transition; and pick the reverse of what hint asked for:
349 */
350 std::swap(tran.atMSecsSinceEpoch, nextTran.atMSecsSinceEpoch);
351 return nextFirst ? tran : nextTran;
352 }
353 // Before first transition, or system has transitions but not for this zone.
354 // Try falling back to offsetFromUtc (works for before first transition, at least).
355 }
356
357 /* Bracket and refine to discover offset. */
358 qint64 utcEpochMSecs;
359
360 int early = offsetFromUtc(recent);
361 int late = offsetFromUtc(imminent);
362 if (early == late // > 99% of the time
363 || late == invalidSeconds()) {
364 if (early == invalidSeconds()
365 || qSubOverflow(forLocalMSecs, early * qint64(1000), &utcEpochMSecs)) {
366 return invalidData(); // Outside representable range
367 }
368 } else {
369 // Close to a DST transition: early > late is near a fall-back,
370 // early < late is near a spring-forward.
371 const int offsetInDst = qMax(early, late);
372 const int offsetInStd = qMin(early, late);
373 // Candidate values for utcEpochMSecs (if forLocalMSecs is valid):
374 const qint64 forDst = forLocalMSecs - offsetInDst * 1000;
375 const qint64 forStd = forLocalMSecs - offsetInStd * 1000;
376 // Best guess at the answer:
377 const qint64 hinted = hint > 0 ? forDst : forStd;
378 if (offsetFromUtc(hinted) == (hint > 0 ? offsetInDst : offsetInStd)) {
379 utcEpochMSecs = hinted;
380 } else if (hint <= 0 && offsetFromUtc(forDst) == offsetInDst) {
381 utcEpochMSecs = forDst;
382 } else if (hint > 0 && offsetFromUtc(forStd) == offsetInStd) {
383 utcEpochMSecs = forStd;
384 } else {
385 // Invalid forLocalMSecs: in spring-forward gap.
386 const int dstStep = (offsetInDst - offsetInStd) * 1000;
387 // That'll typically be the DST offset at imminent, but changes to
388 // standard time have zero DST offset both before and after.
389 Q_ASSERT(dstStep > 0); // There can't be a gap without it !
390 utcEpochMSecs = (hint > 0) ? forStd - dstStep : forDst + dstStep;
391 }
392 }
393
394 return data(utcEpochMSecs);
395}
396
398{
399 return false;
400}
401
403{
404 Q_UNUSED(afterMSecsSinceEpoch);
405 return invalidData();
406}
407
409{
410 Q_UNUSED(beforeMSecsSinceEpoch);
411 return invalidData();
412}
413
415 qint64 toMSecsSinceEpoch) const
416{
418 if (toMSecsSinceEpoch >= fromMSecsSinceEpoch) {
419 // fromMSecsSinceEpoch is inclusive but nextTransitionTime() is exclusive so go back 1 msec
420 Data next = nextTransition(fromMSecsSinceEpoch - 1);
421 while (next.atMSecsSinceEpoch != invalidMSecs()
422 && next.atMSecsSinceEpoch <= toMSecsSinceEpoch) {
424 next = nextTransition(next.atMSecsSinceEpoch);
425 }
426 }
427 return list;
428}
429
431{
432 return QByteArray();
433}
434
436{
437 // Fall-back implementation, can be made faster in subclasses
439 return std::binary_search(tzIds.begin(), tzIds.end(), ianaId);
440}
441
443{
444 return QList<QByteArray>();
445}
446
448{
449 // Default fall-back mode, use the zoneTable to find Region of know Zones
450 QList<QByteArray> regions;
451
452 // First get all Zones in the Zones table belonging to the Region
453 for (const QZoneData &data : zoneDataTable) {
454 if (data.territory == territory) {
455 for (auto l1 : data.ids())
456 regions << QByteArray(l1.data(), l1.size());
457 }
458 }
459
460 std::sort(regions.begin(), regions.end());
461 regions.erase(std::unique(regions.begin(), regions.end()), regions.end());
462
463 // Then select just those that are available
466 result.reserve(qMin(all.size(), regions.size()));
467 std::set_intersection(all.begin(), all.end(), regions.cbegin(), regions.cend(),
468 std::back_inserter(result));
469 return result;
470}
471
473{
474 // Default fall-back mode, use the zoneTable to find Offset of know Zones
476 // First get all Zones in the table using the Offset
477 for (const QWindowsData &winData : windowsDataTable) {
478 if (winData.offsetFromUtc == offsetFromUtc) {
479 for (const QZoneData &data : zoneDataTable) {
480 if (data.windowsIdKey == winData.windowsIdKey) {
481 for (auto l1 : data.ids())
482 offsets << QByteArray(l1.data(), l1.size());
483 }
484 }
485 }
486 }
487
488 std::sort(offsets.begin(), offsets.end());
489 offsets.erase(std::unique(offsets.begin(), offsets.end()), offsets.end());
490
491 // Then select just those that are available
494 result.reserve(qMin(all.size(), offsets.size()));
495 std::set_intersection(all.begin(), all.end(), offsets.cbegin(), offsets.cend(),
496 std::back_inserter(result));
497 return result;
498}
499
500#ifndef QT_NO_DATASTREAM
502{
503 ds << QString::fromUtf8(m_id);
504}
505#endif // QT_NO_DATASTREAM
506
507// Static Utility Methods
508
510{
511 Data data;
512 data.atMSecsSinceEpoch = invalidMSecs();
513 data.offsetFromUtc = invalidSeconds();
514 data.standardTimeOffset = invalidSeconds();
515 data.daylightTimeOffset = invalidSeconds();
516 return data;
517}
518
520{
521 QTimeZone::OffsetData offsetData;
522 offsetData.atUtc = QDateTime();
523 offsetData.offsetFromUtc = invalidSeconds();
524 offsetData.standardTimeOffset = invalidSeconds();
525 offsetData.daylightTimeOffset = invalidSeconds();
526 return offsetData;
527}
528
530{
531 QTimeZone::OffsetData offsetData = invalidOffsetData();
532 if (data.atMSecsSinceEpoch != invalidMSecs()) {
533 offsetData.atUtc = QDateTime::fromMSecsSinceEpoch(data.atMSecsSinceEpoch, QTimeZone::UTC);
534 offsetData.offsetFromUtc = data.offsetFromUtc;
535 offsetData.standardTimeOffset = data.standardTimeOffset;
536 offsetData.daylightTimeOffset = data.daylightTimeOffset;
537 offsetData.abbreviation = data.abbreviation;
538 }
539 return offsetData;
540}
541
542// Is the format of the ID valid ?
544{
545 /*
546 Main rules for defining TZ/IANA names, as per
547 https://www.iana.org/time-zones/repository/theory.html, are:
548 1. Use only valid POSIX file name components
549 2. Within a file name component, use only ASCII letters, `.', `-' and `_'.
550 3. Do not use digits (except in a [+-]\d+ suffix, when used).
551 4. A file name component must not exceed 14 characters or start with `-'
552
553 However, the rules are really guidelines - a later one says
554 - Do not change established names if they only marginally violate the
555 above rules.
556 We may, therefore, need to be a bit slack in our check here, if we hit
557 legitimate exceptions in real time-zone databases. In particular, ICU
558 includes some non-standard names with some components > 14 characters
559 long; so does Android, possibly deriving them from ICU.
560
561 In particular, aliases such as "Etc/GMT+7" and "SystemV/EST5EDT" are valid
562 so we need to accept digits, ':', and '+'; aliases typically have the form
563 of POSIX TZ strings, which allow a suffix to a proper IANA name. A POSIX
564 suffix starts with an offset (as in GMT+7) and may continue with another
565 name (as in EST5EDT, giving the DST name of the zone); a further offset is
566 allowed (for DST). The ("hard to describe and [...] error-prone in
567 practice") POSIX form even allows a suffix giving the dates (and
568 optionally times) of the annual DST transitions. Hopefully, no TZ aliases
569 go that far, but we at least need to accept an offset and (single
570 fragment) DST-name.
571
572 But for the legacy complications, the following would be preferable if
573 QRegExp would work on QByteArrays directly:
574 const QRegExp rx(QStringLiteral("[a-z+._][a-z+._-]{,13}"
575 "(?:/[a-z+._][a-z+._-]{,13})*"
576 // Optional suffix:
577 "(?:[+-]?\d{1,2}(?::\d{1,2}){,2}" // offset
578 // one name fragment (DST):
579 "(?:[a-z+._][a-z+._-]{,13})?)"),
580 Qt::CaseInsensitive);
581 return rx.exactMatch(ianaId);
582 */
583
584 // Somewhat slack hand-rolled version:
585 const int MinSectionLength = 1;
586#if defined(Q_OS_ANDROID) || QT_CONFIG(icu)
587 // Android has its own naming of zones. It may well come from ICU.
588 // "Canada/East-Saskatchewan" has a 17-character second component.
589 const int MaxSectionLength = 17;
590#else
591 const int MaxSectionLength = 14;
592#endif
593 int sectionLength = 0;
594 for (const char *it = ianaId.begin(), * const end = ianaId.end(); it != end; ++it, ++sectionLength) {
595 const char ch = *it;
596 if (ch == '/') {
597 if (sectionLength < MinSectionLength || sectionLength > MaxSectionLength)
598 return false; // violates (4)
599 sectionLength = -1;
600 } else if (ch == '-') {
601 if (sectionLength == 0)
602 return false; // violates (4)
603 } else if (!isAsciiLower(ch)
604 && !isAsciiUpper(ch)
605 && !(ch == '_')
606 && !(ch == '.')
607 // Should ideally check these only happen as an offset:
608 && !isAsciiDigit(ch)
609 && !(ch == '+')
610 && !(ch == ':')) {
611 return false; // violates (2)
612 }
613 }
614 if (sectionLength < MinSectionLength || sectionLength > MaxSectionLength)
615 return false; // violates (4)
616 return true;
617}
618
619QString QTimeZonePrivate::isoOffsetFormat(int offsetFromUtc, QTimeZone::NameType mode)
620{
621 if (mode == QTimeZone::ShortName && !offsetFromUtc)
622 return utcQString();
623
624 char sign = '+';
625 if (offsetFromUtc < 0) {
626 sign = '-';
628 }
629 const int secs = offsetFromUtc % 60;
630 const int mins = (offsetFromUtc / 60) % 60;
631 const int hour = offsetFromUtc / 3600;
632 QString result = QString::asprintf("UTC%c%02d", sign, hour);
633 if (mode != QTimeZone::ShortName || secs || mins)
634 result += QString::asprintf(":%02d", mins);
635 if (mode == QTimeZone::LongName || secs)
636 result += QString::asprintf(":%02d", secs);
637 return result;
638}
639
641{
642 // We don't have a Latin1/UTF-8 mixed comparator (QTBUG-100234),
643 // so we have to allocate here...
644 const auto idUtf8 = QString::fromUtf8(id);
645
646 for (const QZoneData &data : zoneDataTable) {
647 for (auto l1 : data.ids()) {
648 if (l1 == idUtf8)
649 return toWindowsIdLiteral(data.windowsIdKey);
650 }
651 }
652 return QByteArray();
653}
654
656{
657 for (const QWindowsData &data : windowsDataTable) {
658 if (data.windowsId() == windowsId) {
659 QByteArrayView id = data.ianaId();
660 if (qsizetype cut = id.indexOf(' '); cut >= 0)
661 id = id.first(cut);
662 return id.toByteArray();
663 }
664 }
665 return QByteArray();
666}
667
669 QLocale::Territory territory)
670{
672 return list.size() > 0 ? list.first() : QByteArray();
673}
674
676{
677 const quint16 windowsIdKey = toWindowsIdKey(windowsId);
679
680 for (const QZoneData &data : zoneDataTable) {
681 if (data.windowsIdKey == windowsIdKey) {
682 for (auto l1 : data.ids())
683 list << QByteArray(l1.data(), l1.size());
684 }
685 }
686
687 // Return the full list in alpha order
688 std::sort(list.begin(), list.end());
689 return list;
690}
691
693 QLocale::Territory territory)
694{
696 const quint16 windowsIdKey = toWindowsIdKey(windowsId);
697 const qint16 land = static_cast<quint16>(territory);
698 for (const QZoneData &data : zoneDataTable) {
699 // Return the region matches in preference order
700 if (data.windowsIdKey == windowsIdKey && data.territory == land) {
701 for (auto l1 : data.ids())
702 list << QByteArray(l1.data(), l1.size());
703 break;
704 }
705 }
706
707 return list;
708}
709
710// Define template for derived classes to reimplement so QSharedDataPointer clone() works correctly
712{
713 return d->clone();
714}
715
717{
719 while ((cut = ianaIds.indexOf(' ')) >= 0) {
720 if (id == ianaIds.first(cut))
721 return true;
722 ianaIds = ianaIds.sliced(cut);
723 }
724 return id == ianaIds;
725}
726
727/*
728 UTC Offset implementation, used when QT_NO_SYSTEMLOCALE set and ICU is not being used,
729 or for QDateTimes with a Qt:Spec of Qt::OffsetFromUtc.
730*/
731
732// Create default UTC time zone
734{
735 const QString name = utcQString();
737}
738
739// Create a named UTC time zone
741{
742 // Look for the name in the UTC list, if found set the values
743 for (const QUtcData &data : utcDataTable) {
744 if (isEntryInIanaList(id, data.id())) {
746 init(id, data.offsetFromUtc, name, name, QLocale::AnyTerritory, name);
747 break;
748 }
749 }
750}
751
753{
754 // Convert reasonable UTC[+-]\d+(:\d+){,2} to offset in seconds.
755 // Assumption: id has already been tried as a CLDR UTC offset ID (notably
756 // including plain "UTC" itself) and a system offset ID; it's neither.
757 if (!id.startsWith("UTC") || id.size() < 5)
758 return invalidSeconds(); // Doesn't match
759 const char signChar = id.at(3);
760 if (signChar != '-' && signChar != '+')
761 return invalidSeconds(); // No sign
762 const int sign = signChar == '-' ? -1 : 1;
763
764 const auto offsets = id.mid(4).split(':');
765 if (offsets.isEmpty() || offsets.size() > 3)
766 return invalidSeconds(); // No numbers, or too many.
767
768 qint32 seconds = 0;
769 int prior = 0; // Number of fields parsed thus far
770 for (const auto &offset : offsets) {
771 bool ok = false;
772 unsigned short field = offset.toUShort(&ok);
773 // Bound hour above at 24, minutes and seconds at 60:
774 if (!ok || field >= (prior ? 60 : 24))
775 return invalidSeconds();
776 seconds = seconds * 60 + field;
777 ++prior;
778 }
779 while (prior++ < 3)
780 seconds *= 60;
781
782 return seconds * sign;
783}
784
785// Create offset from UTC
787{
788 QString utcId = isoOffsetFormat(offsetSeconds, QTimeZone::ShortName);
789 init(utcId.toUtf8(), offsetSeconds, utcId, utcId, QLocale::AnyTerritory, utcId);
790}
791
793 const QString &name, const QString &abbreviation,
794 QLocale::Territory territory, const QString &comment)
795{
796 init(zoneId, offsetSeconds, name, abbreviation, territory, comment);
797}
798
800 : QTimeZonePrivate(other), m_name(other.m_name),
801 m_abbreviation(other.m_abbreviation),
802 m_comment(other.m_comment),
803 m_territory(other.m_territory),
804 m_offsetFromUtc(other.m_offsetFromUtc)
805{
806}
807
809{
810}
811
813{
814 return new QUtcTimeZonePrivate(*this);
815}
816
818{
819 Data d;
820 d.abbreviation = m_abbreviation;
821 d.atMSecsSinceEpoch = forMSecsSinceEpoch;
822 d.standardTimeOffset = d.offsetFromUtc = m_offsetFromUtc;
823 d.daylightTimeOffset = 0;
824 return d;
825}
826
827void QUtcTimeZonePrivate::init(const QByteArray &zoneId)
828{
829 m_id = zoneId;
830}
831
832void QUtcTimeZonePrivate::init(const QByteArray &zoneId, int offsetSeconds, const QString &name,
833 const QString &abbreviation, QLocale::Territory territory,
834 const QString &comment)
835{
836 m_id = zoneId;
837 m_offsetFromUtc = offsetSeconds;
838 m_name = name;
839 m_abbreviation = abbreviation;
840 m_territory = territory;
841 m_comment = comment;
842}
843
845{
846 return m_territory;
847}
848
850{
851 return m_comment;
852}
853
854QString QUtcTimeZonePrivate::displayName(QTimeZone::TimeType timeType,
855 QTimeZone::NameType nameType,
856 const QLocale &locale) const
857{
858 Q_UNUSED(timeType);
859 Q_UNUSED(locale);
860 if (nameType == QTimeZone::ShortName)
861 return m_abbreviation;
862 else if (nameType == QTimeZone::OffsetName)
863 return isoOffsetFormat(m_offsetFromUtc);
864 return m_name;
865}
866
868{
869 Q_UNUSED(atMSecsSinceEpoch);
870 return m_abbreviation;
871}
872
874{
875 Q_UNUSED(atMSecsSinceEpoch);
876 return m_offsetFromUtc;
877}
878
880{
881 Q_UNUSED(atMSecsSinceEpoch);
882 return 0;
883}
884
886{
887 return utcQByteArray();
888}
889
891{
892 // Only the zone IDs supplied by CLDR and recognized by constructor.
893 for (const QUtcData &data : utcDataTable) {
894 if (isEntryInIanaList(ianaId, data.id()))
895 return true;
896 }
897 // But see offsetFromUtcString(), which lets us accept some "unavailable" IDs.
898 return false;
899}
900
902{
903 // Only the zone IDs supplied by CLDR and recognized by constructor.
905 result.reserve(std::size(utcDataTable));
906 for (const QUtcData &data : utcDataTable) {
907 QByteArrayView id = data.id();
909 while ((cut = id.indexOf(' ')) >= 0) {
910 result << id.first(cut).toByteArray();
911 id = id.sliced(cut);
912 }
913 result << id.toByteArray();
914 }
915 // Not guaranteed to be sorted, so sort:
916 std::sort(result.begin(), result.end());
917 // ### assuming no duplicates
918 return result;
919}
920
922{
923 // If AnyTerritory then is request for all non-region offset codes
924 if (country == QLocale::AnyTerritory)
925 return availableTimeZoneIds();
926 return QList<QByteArray>();
927}
928
930{
931 // Only if it's present in CLDR. (May get more than one ID: UTC, UTC+00:00
932 // and UTC-00:00 all have the same offset.)
934 for (const QUtcData &data : utcDataTable) {
935 if (data.offsetFromUtc == offsetSeconds) {
936 QByteArrayView id = data.id();
938 while ((cut = id.indexOf(' ')) >= 0) {
939 result << id.first(cut).toByteArray();
940 id = id.sliced(cut);
941 }
942 result << id.toByteArray();
943 }
944 }
945 // Not guaranteed to be sorted, so sort:
946 std::sort(result.begin(), result.end());
947 // ### assuming no duplicates
948 return result;
949}
950
951#ifndef QT_NO_DATASTREAM
953{
954 ds << QStringLiteral("OffsetFromUtc") << QString::fromUtf8(m_id) << m_offsetFromUtc << m_name
955 << m_abbreviation << static_cast<qint32>(m_territory) << m_comment;
956}
957#endif // QT_NO_DATASTREAM
958
constexpr QByteArrayView sliced(qsizetype pos) const
constexpr QByteArrayView first(qsizetype n) const
qsizetype indexOf(QByteArrayView a, qsizetype from=0) const noexcept
\inmodule QtCore
Definition qbytearray.h:57
char * data()
\macro QT_NO_CAST_FROM_BYTEARRAY
Definition qbytearray.h:534
qsizetype size() const noexcept
Returns the number of bytes in this byte array.
Definition qbytearray.h:474
iterator end()
Returns an \l{STL-style iterators}{STL-style iterator} pointing just after the last byte in the byte-...
Definition qbytearray.h:432
bool isEmpty() const noexcept
Returns true if the byte array has size 0; otherwise returns false.
Definition qbytearray.h:106
iterator begin()
Returns an \l{STL-style iterators}{STL-style iterator} pointing to the first byte in the byte-array.
Definition qbytearray.h:428
\inmodule QtCore\reentrant
Definition qdatastream.h:30
\inmodule QtCore\reentrant
Definition qdatetime.h:257
static QDateTime fromMSecsSinceEpoch(qint64 msecs, const QTimeZone &timeZone)
qsizetype size() const noexcept
Definition qlist.h:386
T & first()
Definition qlist.h:628
iterator erase(const_iterator begin, const_iterator end)
Definition qlist.h:882
iterator end()
Definition qlist.h:609
iterator begin()
Definition qlist.h:608
const_iterator cend() const noexcept
Definition qlist.h:614
void append(parameter_type t)
Definition qlist.h:441
const_iterator cbegin() const noexcept
Definition qlist.h:613
Country Territory
Definition qlocale.h:851
@ AnyTerritory
Definition qlocale.h:558
\inmodule QtCore
Definition qshareddata.h:19
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:127
static QString fromUtf8(QByteArrayView utf8)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:5857
QByteArray toUtf8() const &
Definition qstring.h:563
static QString static QString asprintf(const char *format,...) Q_ATTRIBUTE_FORMAT_PRINTF(1
Definition qstring.cpp:7005
virtual bool hasDaylightTime() const
virtual bool isDaylightTime(qint64 atMSecsSinceEpoch) const
static QByteArray utcQByteArray()
virtual QTimeZonePrivate * clone() const
static QByteArray windowsIdToDefaultIanaId(const QByteArray &windowsId)
virtual Data nextTransition(qint64 afterMSecsSinceEpoch) const
static Data invalidData()
virtual Data data(qint64 forMSecsSinceEpoch) const
virtual bool hasTransitions() const
Data dataForLocalTime(qint64 forLocalMSecs, int hint) const
virtual Data previousTransition(qint64 beforeMSecsSinceEpoch) const
virtual QString displayName(qint64 atMSecsSinceEpoch, QTimeZone::NameType nameType, const QLocale &locale) const
bool operator==(const QTimeZonePrivate &other) const
virtual int daylightTimeOffset(qint64 atMSecsSinceEpoch) const
bool operator!=(const QTimeZonePrivate &other) const
static constexpr qint64 invalidMSecs()
virtual bool isTimeZoneIdAvailable(const QByteArray &ianaId) const
virtual QLocale::Territory territory() const
virtual QString comment() const
static constexpr qint64 maxMSecs()
virtual void serialize(QDataStream &ds) const
QByteArray id() const
static QList< QByteArray > windowsIdToIanaIds(const QByteArray &windowsId)
virtual QList< QByteArray > availableTimeZoneIds() const
virtual QString abbreviation(qint64 atMSecsSinceEpoch) const
static QTimeZone::OffsetData toOffsetData(const Data &data)
DataList transitions(qint64 fromMSecsSinceEpoch, qint64 toMSecsSinceEpoch) const
virtual int standardTimeOffset(qint64 atMSecsSinceEpoch) const
static QByteArray ianaIdToWindowsId(const QByteArray &ianaId)
static QString isoOffsetFormat(int offsetFromUtc, QTimeZone::NameType mode=QTimeZone::OffsetName)
static constexpr qint64 minMSecs()
virtual int offsetFromUtc(qint64 atMSecsSinceEpoch) const
static QTimeZone::OffsetData invalidOffsetData()
static constexpr qint64 invalidSeconds()
static QString utcQString()
virtual QByteArray systemTimeZoneId() const
static bool isValidId(const QByteArray &ianaId)
static constexpr int MaxUtcOffsetSecs
Definition qtimezone.h:89
static constexpr int MinUtcOffsetSecs
Definition qtimezone.h:86
QUtcTimeZonePrivate * clone() const override
int standardTimeOffset(qint64 atMSecsSinceEpoch) const override
QList< QByteArray > availableTimeZoneIds() const override
static qint64 offsetFromUtcString(const QByteArray &id)
bool isTimeZoneIdAvailable(const QByteArray &ianaId) const override
QLocale::Territory territory() const override
QString abbreviation(qint64 atMSecsSinceEpoch) const override
Data data(qint64 forMSecsSinceEpoch) const override
void serialize(QDataStream &ds) const override
QString comment() const override
QString displayName(QTimeZone::TimeType timeType, QTimeZone::NameType nameType, const QLocale &locale) const override
int daylightTimeOffset(qint64 atMSecsSinceEpoch) const override
QByteArray systemTimeZoneId() const override
QSet< QString >::iterator it
short next
Definition keywords.cpp:445
Token token
Definition keywords.cpp:444
Combined button and popup list for selecting options.
constexpr bool isAsciiDigit(char32_t c) noexcept
Definition qtools_p.h:67
constexpr bool isAsciiLower(char32_t c) noexcept
Definition qtools_p.h:77
constexpr bool isAsciiUpper(char32_t c) noexcept
Definition qtools_p.h:72
static jboolean cut(JNIEnv *, jobject)
constexpr const T & qMin(const T &a, const T &b)
Definition qminmax.h:40
constexpr const T & qMax(const T &a, const T &b)
Definition qminmax.h:42
std::enable_if_t< std::is_unsigned_v< T >, bool > qAddOverflow(T v1, T v2, T *r)
Definition qnumeric.h:113
std::enable_if_t< std::is_unsigned_v< T >, bool > qSubOverflow(T v1, T v2, T *r)
Definition qnumeric.h:153
GLenum mode
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLuint GLuint end
GLenum GLenum dst
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLenum GLuint GLintptr offset
GLuint name
GLint first
GLuint GLsizei const GLuint const GLintptr * offsets
GLuint64EXT * result
[6]
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
#define QStringLiteral(str)
static QT_BEGIN_NAMESPACE QVariant hint(QPlatformIntegration::StyleHint h)
static bool isEntryInIanaList(QByteArrayView id, QByteArrayView ianaIds)
static quint16 toWindowsIdKey(const QByteArray &winId)
static QByteArray toWindowsIdLiteral(quint16 windowsIdKey)
static constexpr QWindowsData windowsDataTable[]
static constexpr QUtcData utcDataTable[]
static constexpr QZoneData zoneDataTable[]
#define Q_UNUSED(x)
short qint16
Definition qtypes.h:42
unsigned short quint16
Definition qtypes.h:43
int qint32
Definition qtypes.h:44
ptrdiff_t qsizetype
Definition qtypes.h:70
long long qint64
Definition qtypes.h:55
static int sign(int x)
QList< int > list
[14]
list indexOf("B")
QSharedPointer< T > other(t)
[5]