Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
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"
8#if QT_CONFIG(timezone_locale)
9# include "qtimezonelocale_p.h"
10#endif
12
13#include <qdatastream.h>
14#include <qdebug.h>
15#include <qstring.h>
16
17#include <private/qcalendarmath_p.h>
18#include <private/qnumeric_p.h>
19#include <private/qtools_p.h>
20
21#include <algorithm>
22
24
25using namespace QtMiscUtils;
26using namespace QtTimeZoneCldr;
27using namespace Qt::StringLiterals;
28
29// For use with std::is_sorted() in assertions:
30[[maybe_unused]]
31constexpr bool earlierZoneData(const ZoneData &less, const ZoneData &more) noexcept
32{
33 return less.windowsIdKey < more.windowsIdKey
34 || (less.windowsIdKey == more.windowsIdKey && less.territory < more.territory);
35}
36
37[[maybe_unused]]
38static bool earlierWinData(const WindowsData &less, const WindowsData &more) noexcept
39{
40 // Actually only tested in the negative, to check more < less never happens,
41 // so should be true if more < less in either part; hence || not && combines.
42 return less.windowsIdKey < more.windowsIdKey
43 || less.windowsId().compare(more.windowsId(), Qt::CaseInsensitive) < 0;
44}
45
46// For use with std::lower_bound():
47constexpr bool atLowerUtcOffset(const UtcData &entry, qint32 offsetSeconds) noexcept
48{
49 return entry.offsetFromUtc < offsetSeconds;
50}
51
52constexpr bool atLowerWindowsKey(const WindowsData &entry, qint16 winIdKey) noexcept
53{
54 return entry.windowsIdKey < winIdKey;
55}
56
57static bool earlierWindowsId(const WindowsData &entry, QByteArrayView winId) noexcept
58{
59 return entry.windowsId().compare(winId, Qt::CaseInsensitive) < 0;
60}
61
62constexpr bool zoneAtLowerWindowsKey(const ZoneData &entry, qint16 winIdKey) noexcept
63{
64 return entry.windowsIdKey < winIdKey;
65}
66
67// Static table-lookup helpers
68static quint16 toWindowsIdKey(const QByteArray &winId)
69{
70 // Key and winId are monotonic, table is sorted on them.
71 const auto data = std::lower_bound(std::begin(windowsDataTable), std::end(windowsDataTable),
72 winId, earlierWindowsId);
73 if (data != std::end(windowsDataTable) && data->windowsId() == winId)
74 return data->windowsIdKey;
75 return 0;
76}
77
79{
80 // Caller should be passing a valid (in range) key; and table is sorted in
81 // increasing order, with no gaps in numbering, starting with key = 1 at
82 // index [0]. So this should normally work:
83 if (Q_LIKELY(windowsIdKey > 0 && windowsIdKey <= std::size(windowsDataTable))) {
84 const auto &data = windowsDataTable[windowsIdKey - 1];
85 if (Q_LIKELY(data.windowsIdKey == windowsIdKey))
86 return data.windowsId().toByteArray();
87 }
88 // Fall back on binary chop - key and winId are monotonic, table is sorted on them:
89 const auto data = std::lower_bound(std::begin(windowsDataTable), std::end(windowsDataTable),
90 windowsIdKey, atLowerWindowsKey);
91 if (data != std::end(windowsDataTable) && data->windowsIdKey == windowsIdKey)
92 return data->windowsId().toByteArray();
93
94 return QByteArray();
95}
96
97static auto zoneStartForWindowsId(quint16 windowsIdKey) noexcept
98{
99 // Caller must check the resulting iterator isn't std::end(zoneDataTable)
100 // and does match windowsIdKey, since this is just the lower bound.
101 return std::lower_bound(std::begin(zoneDataTable), std::end(zoneDataTable),
102 windowsIdKey, zoneAtLowerWindowsKey);
103}
104
105/*
106 Base class implementing common utility routines, only instantiate for a null tz.
107*/
108
110{
111 // If std::is_sorted() were constexpr, the first could be a static_assert().
112 // From C++20, we should be able to rework it in terms of std::all_of().
113 Q_ASSERT(std::is_sorted(std::begin(zoneDataTable), std::end(zoneDataTable),
115 Q_ASSERT(std::is_sorted(std::begin(windowsDataTable), std::end(windowsDataTable),
117}
118
123
127
129{
130 return new QTimeZonePrivate(*this);
131}
132
134{
135 // TODO Too simple, but need to solve problem of comparing different derived classes
136 // Should work for all System and ICU classes as names guaranteed unique, but not for Simple.
137 // Perhaps once all classes have working transitions can compare full list?
138 return (m_id == other.m_id);
139}
140
142{
143 return !(*this == other);
144}
145
147{
148 return !m_id.isEmpty();
149}
150
152{
153 return m_id;
154}
155
157{
158 // Default fall-back mode, use the zoneTable to find Region of known Zones
159 const QLatin1StringView sought(m_id.data(), m_id.size());
160 for (const ZoneData &data : zoneDataTable) {
161 for (QLatin1StringView token : data.ids()) {
162 if (token == sought)
163 return QLocale::Territory(data.territory);
164 }
165 }
167}
168
170{
171 return QString();
172}
173
175 QTimeZone::NameType nameType,
176 const QLocale &locale) const
177{
178 const Data tran = data(atMSecsSinceEpoch);
179 if (tran.atMSecsSinceEpoch != invalidMSecs()) {
180 if (nameType == QTimeZone::OffsetName && locale.language() == QLocale::C)
181 return isoOffsetFormat(tran.offsetFromUtc);
182 if (nameType == QTimeZone::ShortName && isDataLocale(locale))
183 return tran.abbreviation;
184
185 QTimeZone::TimeType timeType
186 = tran.daylightTimeOffset != 0 ? QTimeZone::DaylightTime : QTimeZone::StandardTime;
187#if QT_CONFIG(timezone_locale)
188 return localeName(atMSecsSinceEpoch, tran.offsetFromUtc, timeType, nameType, locale);
189#else
190 return displayName(timeType, nameType, locale);
191#endif
192 }
193 return QString();
194}
195
196QString QTimeZonePrivate::displayName(QTimeZone::TimeType timeType,
197 QTimeZone::NameType nameType,
198 const QLocale &locale) const
199{
200 const Data tran = data(timeType);
201 if (tran.atMSecsSinceEpoch != invalidMSecs()) {
202 if (nameType == QTimeZone::OffsetName && isDataLocale(locale))
203 return isoOffsetFormat(tran.offsetFromUtc);
204
205#if QT_CONFIG(timezone_locale)
206 return localeName(tran.atMSecsSinceEpoch, tran.offsetFromUtc, timeType, nameType, locale);
207#endif
208 }
209 return QString();
210}
211
213{
214 return displayName(atMSecsSinceEpoch, QTimeZone::ShortName, QLocale::c());
215}
216
217int QTimeZonePrivate::offsetFromUtc(qint64 atMSecsSinceEpoch) const
218{
219 const int std = standardTimeOffset(atMSecsSinceEpoch);
220 const int dst = daylightTimeOffset(atMSecsSinceEpoch);
221 const int bad = invalidSeconds();
222 return std == bad || dst == bad ? bad : std + dst;
223}
224
226{
227 Q_UNUSED(atMSecsSinceEpoch);
228 return invalidSeconds();
229}
230
232{
233 Q_UNUSED(atMSecsSinceEpoch);
234 return invalidSeconds();
235}
236
238{
239 return false;
240}
241
242bool QTimeZonePrivate::isDaylightTime(qint64 atMSecsSinceEpoch) const
243{
244 Q_UNUSED(atMSecsSinceEpoch);
245 return false;
246}
247
248QTimeZonePrivate::Data QTimeZonePrivate::data(QTimeZone::TimeType timeType) const
249{
250 // True if tran is valid and has the DST-ness to match timeType:
251 const auto validMatch = [timeType](const QTimeZonePrivate::Data &tran) {
252 return tran.atMSecsSinceEpoch != invalidMSecs()
253 && ((timeType == QTimeZone::DaylightTime) != (tran.daylightTimeOffset == 0));
254 };
255
256 // Get current tran, use if suitable:
257 const qint64 currentMSecs = QDateTime::currentMSecsSinceEpoch();
258 QTimeZonePrivate::Data tran = data(currentMSecs);
259 if (validMatch(tran))
260 return tran;
261
262 if (hasTransitions()) {
263 // Otherwise, next tran probably flips DST-ness:
264 tran = nextTransition(currentMSecs);
265 if (validMatch(tran))
266 return tran;
267
268 // Failing that, prev (or present, if current MSecs is exactly a
269 // transition moment) tran defines what data() got us and the one before
270 // that probably flips DST-ness; failing that, keep marching backwards
271 // in search of a DST interval:
272 tran = previousTransition(currentMSecs + 1);
273 while (tran.atMSecsSinceEpoch != invalidMSecs()) {
274 tran = previousTransition(tran.atMSecsSinceEpoch);
275 if (validMatch(tran))
276 return tran;
277 }
278 }
279 return {};
280}
281
293{
294 // Guess data is for the system locale unless backend overrides that.
295 return locale == QLocale::system();
296}
297
299{
300 Q_UNUSED(forMSecsSinceEpoch);
301 return {};
302}
303
304// Private only method for use by QDateTime to convert local msecs to epoch msecs
306 qint64 forLocalMSecs, QDateTimePrivate::TransitionOptions resolve) const
307{
308 auto dataToState = [](const QTimeZonePrivate::Data &d) {
309 return QDateTimePrivate::ZoneState(d.atMSecsSinceEpoch + d.offsetFromUtc * 1000,
310 d.offsetFromUtc,
311 d.daylightTimeOffset ? QDateTimePrivate::DaylightTime
313 };
314
315 /*
316 We need a UTC time at which to ask for the offset, in order to be able to
317 add that offset to forLocalMSecs, to get the UTC time we need.
318 Fortunately, all time-zone offsets have been less than 17 hours; and DST
319 transitions happen (much) more than thirty-four hours apart. So sampling
320 offset seventeen hours each side gives us information we can be sure
321 brackets the correct time and at most one DST transition.
322 */
323 std::integral_constant<qint64, 17 * 3600 * 1000> seventeenHoursInMSecs;
324 static_assert(-seventeenHoursInMSecs / 1000 < QTimeZone::MinUtcOffsetSecs
325 && seventeenHoursInMSecs / 1000 > QTimeZone::MaxUtcOffsetSecs);
326 qint64 millis;
327 // Clip the bracketing times to the bounds of the supported range.
328 const qint64 recent =
329 qSubOverflow(forLocalMSecs, seventeenHoursInMSecs, &millis) || millis < minMSecs()
330 ? minMSecs() : millis; // Necessarily <= forLocalMSecs + 1.
331 // (Given that minMSecs() is std::numeric_limits<qint64>::min() + 1.)
332 const qint64 imminent =
333 qAddOverflow(forLocalMSecs, seventeenHoursInMSecs, &millis)
334 ? maxMSecs() : millis; // Necessarily >= forLocalMSecs
335 // At most one of those was clipped to its boundary value:
336 Q_ASSERT(recent < imminent && seventeenHoursInMSecs < imminent - recent + 1);
337
338 const Data past = data(recent), future = data(imminent);
339 // > 99% of the time, past and future will agree:
340 if (Q_LIKELY(past.offsetFromUtc == future.offsetFromUtc
341 && past.standardTimeOffset == future.standardTimeOffset
342 // Those two imply same daylightTimeOffset.
343 && past.abbreviation == future.abbreviation)) {
344 Data data = future;
345 data.atMSecsSinceEpoch = forLocalMSecs - future.offsetFromUtc * 1000;
346 return dataToState(data);
347 }
348
349 /*
350 Offsets are Local - UTC, positive to the east of Greenwich, negative to
351 the west; DST offset normally exceeds standard offset, when DST applies.
352 When we have offsets on either side of a transition, the lower one is
353 standard, the higher is DST, unless we have data telling us it's the other
354 way round.
355
356 Non-DST transitions (jurisdictions changing time-zone and time-zones
357 changing their standard offset, typically) are described below as if they
358 were DST transitions (since these are more usual and familiar); the code
359 mostly concerns itself with offsets from UTC, described in terms of the
360 common case for changes in that. If there is no actual change in offset
361 (e.g. a DST transition cancelled by a standard offset change), this code
362 should handle it gracefully; without transitions, it'll see early == late
363 and take the easy path; with transitions, tran and nextTran get the
364 correct UTC time as atMSecsSinceEpoch so comparing to nextStart selects
365 the right one. In all other cases, the transition changes offset and the
366 reasoning that applies to DST applies just the same.
367
368 The resolution of transitions, specified by \a resolve, may be lead astray
369 if (as happens on Windows) the backend has been obliged to guess whether a
370 transition is in fact a DST one or a change to standard offset; or to
371 guess that the higher-offset side is the DST one (the reverse of this is
372 true for Ireland, using negative DST). There's not much we can do about
373 that, though.
374 */
375 if (hasTransitions()) {
376 /*
377 We have transitions.
378
379 Each transition gives the offsets to use until the next; so we need
380 the most recent transition before the time forLocalMSecs describes. If
381 it describes a time *in* a transition, we'll need both that transition
382 and the one before it. So find one transition that's probably after
383 (and not much before, otherwise) and another that's definitely before,
384 then work out which one to use. When both or neither work on
385 forLocalMSecs, use resolve to disambiguate.
386 */
387
388 // Get a transition definitely before the local MSecs; usually all we need.
389 // Only around the transition times might we need another.
390 Data tran = past; // Data after last transition before our window.
391 Q_ASSERT(forLocalMSecs < 0 || // Pre-epoch TZ info may be unavailable
392 forLocalMSecs - tran.offsetFromUtc * 1000 >= tran.atMSecsSinceEpoch);
393 // If offset actually exceeds 17 hours, that assert may trigger.
394 Data nextTran = nextTransition(tran.atMSecsSinceEpoch);
395 /*
396 Now walk those forward until they bracket forLocalMSecs with transitions.
397
398 One of the transitions should then be telling us the right offset to use.
399 In a transition, we need the transition before it (to describe the run-up
400 to the transition) and the transition itself; so we need to stop when
401 nextTran is (invalid or) that transition.
402 */
403 while (nextTran.atMSecsSinceEpoch != invalidMSecs()
404 && forLocalMSecs > nextTran.atMSecsSinceEpoch + nextTran.offsetFromUtc * 1000) {
405 Data newTran = nextTransition(nextTran.atMSecsSinceEpoch);
406 if (newTran.atMSecsSinceEpoch == invalidMSecs()
407 || newTran.atMSecsSinceEpoch + newTran.offsetFromUtc * 1000 > imminent) {
408 // Definitely not a relevant tansition: too far in the future.
409 break;
410 }
411 tran = nextTran;
412 nextTran = newTran;
413 }
414
415 // Check we do *really* have transitions for this zone:
416 if (tran.atMSecsSinceEpoch != invalidMSecs()) {
417 /* So now tran is definitely before ... */
418 Q_ASSERT(forLocalMSecs < 0
419 || forLocalMSecs - tran.offsetFromUtc * 1000 > tran.atMSecsSinceEpoch);
420 // Work out the UTC value it would make sense to return if using tran:
421 tran.atMSecsSinceEpoch = forLocalMSecs - tran.offsetFromUtc * 1000;
422 // If we know of no transition after it, the answer is easy:
423 const qint64 nextStart = nextTran.atMSecsSinceEpoch;
424 if (nextStart == invalidMSecs())
425 return dataToState(tran); // Last valid transition.
426
427 /*
428 ... and nextTran is either after or only slightly before. We're
429 going to interpret one as standard time, the other as DST
430 (although the transition might in fact be a change in standard
431 offset, or a change in DST offset, e.g. to/from double-DST).
432
433 Usually exactly one of those shall be relevant and we'll use it;
434 but if we're close to nextTran we may be in a transition, to be
435 settled according to resolve's rules.
436 */
437 // Work out the UTC value it would make sense to return if using nextTran:
438 nextTran.atMSecsSinceEpoch = forLocalMSecs - nextTran.offsetFromUtc * 1000;
439
440 bool fallBack = false;
441 if (nextStart > nextTran.atMSecsSinceEpoch) {
442 // If both UTC values are before nextTran's offset applies, use tran:
443 if (nextStart > tran.atMSecsSinceEpoch)
444 return dataToState(tran);
445
446 Q_ASSERT(tran.offsetFromUtc < nextTran.offsetFromUtc);
447 // We're in a spring-forward.
448 } else if (nextStart <= tran.atMSecsSinceEpoch) {
449 // Both UTC values say we should be using nextTran:
450 return dataToState(nextTran);
451 } else {
452 Q_ASSERT(nextTran.offsetFromUtc < tran.offsetFromUtc);
453 fallBack = true; // We're in a fall-back.
454 }
455 // (forLocalMSecs - nextStart) / 1000 lies between the two offsets.
456
457 // Apply resolve:
458 // Determine whether FlipForReverseDst affects the outcome:
459 const bool flipped
461 && (fallBack ? !tran.daylightTimeOffset && nextTran.daylightTimeOffset
462 : tran.daylightTimeOffset && !nextTran.daylightTimeOffset);
463
464 if (fallBack) {
465 if (resolve.testFlag(flipped
468 return dataToState(nextTran);
469 }
470 if (resolve.testFlag(flipped
473 return dataToState(tran);
474 }
475 } else {
476 /* Neither is valid (e.g. in a spring-forward's gap) and
477 nextTran.atMSecsSinceEpoch < nextStart <= tran.atMSecsSinceEpoch.
478 So swap their atMSecsSinceEpoch to give each a moment on the
479 side of the transition that it describes, then select the one
480 after or before according to the option set:
481 */
482 std::swap(tran.atMSecsSinceEpoch, nextTran.atMSecsSinceEpoch);
483 if (resolve.testFlag(flipped
486 return dataToState(nextTran);
487 if (resolve.testFlag(flipped
490 return dataToState(tran);
491 }
492 // Reject
493 return {forLocalMSecs};
494 }
495 // Before first transition, or system has transitions but not for this zone.
496 // Try falling back to offsetFromUtc (works for before first transition, at least).
497 }
498
499 /* Bracket and refine to discover offset. */
500 qint64 utcEpochMSecs;
501
502 // We don't have true data on DST-ness, so can't apply FlipForReverseDst.
503 int early = past.offsetFromUtc;
504 int late = future.offsetFromUtc;
505 if (early == late || late == invalidSeconds()) {
506 if (early == invalidSeconds()
507 || qSubOverflow(forLocalMSecs, early * qint64(1000), &utcEpochMSecs)) {
508 return {forLocalMSecs}; // Outside representable range
509 }
510 } else {
511 // Candidate values for utcEpochMSecs (if forLocalMSecs is valid):
512 const qint64 forEarly = forLocalMSecs - early * 1000;
513 const qint64 forLate = forLocalMSecs - late * 1000;
514 // If either of those doesn't have the offset we got it from, it's on
515 // the wrong side of the transition (and both may be, for a gap):
516 const bool earlyOk = offsetFromUtc(forEarly) == early;
517 const bool lateOk = offsetFromUtc(forLate) == late;
518
519 if (earlyOk) {
520 if (lateOk) {
521 Q_ASSERT(early > late);
522 // fall-back's repeated interval
524 utcEpochMSecs = forEarly;
525 else if (resolve.testFlag(QDateTimePrivate::FoldUseAfter))
526 utcEpochMSecs = forLate;
527 else
528 return {forLocalMSecs};
529 } else {
530 // Before and clear of the transition:
531 utcEpochMSecs = forEarly;
532 }
533 } else if (lateOk) {
534 // After and clear of the transition:
535 utcEpochMSecs = forLate;
536 } else {
537 // forLate <= gap < forEarly
538 Q_ASSERT(late > early);
539 const int dstStep = (late - early) * 1000;
541 utcEpochMSecs = forEarly - dstStep;
542 else if (resolve.testFlag(QDateTimePrivate::GapUseAfter))
543 utcEpochMSecs = forLate + dstStep;
544 else
545 return {forLocalMSecs};
546 }
547 }
548
549 return dataToState(data(utcEpochMSecs));
550}
551
553{
554 return false;
555}
556
558{
559 Q_UNUSED(afterMSecsSinceEpoch);
560 return {};
561}
562
564{
565 Q_UNUSED(beforeMSecsSinceEpoch);
566 return {};
567}
568
570 qint64 toMSecsSinceEpoch) const
571{
573 if (toMSecsSinceEpoch >= fromMSecsSinceEpoch) {
574 // fromMSecsSinceEpoch is inclusive but nextTransitionTime() is exclusive so go back 1 msec
575 Data next = nextTransition(fromMSecsSinceEpoch - 1);
576 while (next.atMSecsSinceEpoch != invalidMSecs()
577 && next.atMSecsSinceEpoch <= toMSecsSinceEpoch) {
579 next = nextTransition(next.atMSecsSinceEpoch);
580 }
581 }
582 return list;
583}
584
589
591{
592 // Fall-back implementation, can be made faster in subclasses.
593 // Backends that don't cache the available list SHOULD override this.
594 const QList<QByteArray> tzIds = availableTimeZoneIds();
595 return std::binary_search(tzIds.begin(), tzIds.end(), ianaId);
596}
597
599{
600 return QList<QByteArray>();
601}
602
603static QList<QByteArray> selectAvailable(QList<QByteArray>&& desired, const QList<QByteArray>& all)
604{
605 std::sort(desired.begin(), desired.end());
606 const auto newEnd = std::unique(desired.begin(), desired.end());
607 const auto newSize = std::distance(desired.begin(), newEnd);
608 QList<QByteArray> result;
609 result.reserve(qMin(all.size(), newSize));
610 std::set_intersection(all.begin(), all.end(), desired.cbegin(),
611 std::next(desired.cbegin(), newSize), std::back_inserter(result));
612 return result;
613}
614
616{
617 // Default fall-back mode, use the zoneTable to find Region of know Zones
618 QList<QByteArray> regions;
619
620 // First get all Zones in the Zones table belonging to the Region
621 for (const ZoneData &data : zoneDataTable) {
622 if (data.territory == territory) {
623 for (auto l1 : data.ids())
624 regions << QByteArray(l1.data(), l1.size());
625 }
626 }
627 return selectAvailable(std::move(regions), availableTimeZoneIds());
628}
629
630QList<QByteArray> QTimeZonePrivate::availableTimeZoneIds(int offsetFromUtc) const
631{
632 // Default fall-back mode, use the zoneTable to find Offset of know Zones
633 QList<QByteArray> offsets;
634 // First get all Zones in the table using the Offset
635 for (const WindowsData &winData : windowsDataTable) {
636 if (winData.offsetFromUtc == offsetFromUtc) {
637 for (auto data = zoneStartForWindowsId(winData.windowsIdKey);
638 data != std::end(zoneDataTable) && data->windowsIdKey == winData.windowsIdKey;
639 ++data) {
640 for (auto l1 : data->ids())
641 offsets << QByteArray(l1.data(), l1.size());
642 }
643 }
644 }
645 return selectAvailable(std::move(offsets), availableTimeZoneIds());
646}
647
648#ifndef QT_NO_DATASTREAM
650{
651 ds << QString::fromUtf8(m_id);
652}
653#endif // QT_NO_DATASTREAM
654
655// Static Utility Methods
656
658{
659 return { QString(), QDateTime(),
661}
662
664{
665 if (data.atMSecsSinceEpoch == invalidMSecs())
666 return invalidOffsetData();
667
668 return {
669 data.abbreviation,
671 data.offsetFromUtc, data.standardTimeOffset, data.daylightTimeOffset };
672}
673
674// Is the format of the ID valid ?
676{
677 /*
678 Main rules for defining TZ/IANA names, as per
679 https://www.iana.org/time-zones/repository/theory.html, are:
680 1. Use only valid POSIX file name components
681 2. Within a file name component, use only ASCII letters, `.', `-' and `_'.
682 3. Do not use digits (except in a [+-]\d+ suffix, when used).
683 4. A file name component must not exceed 14 characters or start with `-'
684
685 However, the rules are really guidelines - a later one says
686 - Do not change established names if they only marginally violate the
687 above rules.
688 We may, therefore, need to be a bit slack in our check here, if we hit
689 legitimate exceptions in real time-zone databases. In particular, ICU
690 includes some non-standard names with some components > 14 characters
691 long; so does Android, possibly deriving them from ICU.
692
693 In particular, aliases such as "Etc/GMT+7" and "SystemV/EST5EDT" are valid
694 so we need to accept digits, ':', and '+'; aliases typically have the form
695 of POSIX TZ strings, which allow a suffix to a proper IANA name. A POSIX
696 suffix starts with an offset (as in GMT+7) and may continue with another
697 name (as in EST5EDT, giving the DST name of the zone); a further offset is
698 allowed (for DST). The ("hard to describe and [...] error-prone in
699 practice") POSIX form even allows a suffix giving the dates (and
700 optionally times) of the annual DST transitions. Hopefully, no TZ aliases
701 go that far, but we at least need to accept an offset and (single
702 fragment) DST-name.
703
704 But for the legacy complications, the following would be preferable if
705 QRegExp would work on QByteArrays directly:
706 const QRegExp rx(QStringLiteral("[a-z+._][a-z+._-]{,13}"
707 "(?:/[a-z+._][a-z+._-]{,13})*"
708 // Optional suffix:
709 "(?:[+-]?\d{1,2}(?::\d{1,2}){,2}" // offset
710 // one name fragment (DST):
711 "(?:[a-z+._][a-z+._-]{,13})?)"),
712 Qt::CaseInsensitive);
713 return rx.exactMatch(ianaId);
714 */
715
716 // Somewhat slack hand-rolled version:
717 const int MinSectionLength = 1;
718#if defined(Q_OS_ANDROID) || QT_CONFIG(icu)
719 // Android has its own naming of zones. It may well come from ICU.
720 // "Canada/East-Saskatchewan" has a 17-character second component.
721 const int MaxSectionLength = 17;
722#else
723 const int MaxSectionLength = 14;
724#endif
725 int sectionLength = 0;
726 for (const char *it = ianaId.begin(), * const end = ianaId.end(); it != end; ++it, ++sectionLength) {
727 const char ch = *it;
728 if (ch == '/') {
729 if (sectionLength < MinSectionLength || sectionLength > MaxSectionLength)
730 return false; // violates (4)
731 sectionLength = -1;
732 } else if (ch == '-') {
733 if (sectionLength == 0)
734 return false; // violates (4)
735 } else if (!isAsciiLower(ch)
736 && !isAsciiUpper(ch)
737 && !(ch == '_')
738 && !(ch == '.')
739 // Should ideally check these only happen as an offset:
740 && !isAsciiDigit(ch)
741 && !(ch == '+')
742 && !(ch == ':')) {
743 return false; // violates (2)
744 }
745 }
746 if (sectionLength < MinSectionLength || sectionLength > MaxSectionLength)
747 return false; // violates (4)
748 return true;
749}
750
751QString QTimeZonePrivate::isoOffsetFormat(int offsetFromUtc, QTimeZone::NameType mode)
752{
753 if (mode == QTimeZone::ShortName && !offsetFromUtc)
754 return utcQString();
755
756 char sign = '+';
757 if (offsetFromUtc < 0) {
758 sign = '-';
760 }
761 const int secs = offsetFromUtc % 60;
762 const int mins = (offsetFromUtc / 60) % 60;
763 const int hour = offsetFromUtc / 3600;
764 QString result = QString::asprintf("UTC%c%02d", sign, hour);
765 if (mode != QTimeZone::ShortName || secs || mins)
766 result += QString::asprintf(":%02d", mins);
767 if (mode == QTimeZone::LongName || secs)
768 result += QString::asprintf(":%02d", secs);
769 return result;
770}
771
773{
774 const auto idUtf8 = QUtf8StringView(id);
775
776 for (const ZoneData &data : zoneDataTable) {
777 for (auto l1 : data.ids()) {
778 if (l1 == idUtf8)
779 return toWindowsIdLiteral(data.windowsIdKey);
780 }
781 }
782 return QByteArray();
783}
784
786{
787 const auto data = std::lower_bound(std::begin(windowsDataTable), std::end(windowsDataTable),
788 windowsId, earlierWindowsId);
789 if (data != std::end(windowsDataTable) && data->windowsId() == windowsId) {
790 QByteArrayView id = data->ianaId();
791 if (qsizetype cut = id.indexOf(' '); cut >= 0)
792 id = id.first(cut);
793 return id.toByteArray();
794 }
795 return QByteArray();
796}
797
799 QLocale::Territory territory)
800{
801 const QList<QByteArray> list = windowsIdToIanaIds(windowsId, territory);
802 return list.size() > 0 ? list.first() : QByteArray();
803}
804
805QList<QByteArray> QTimeZonePrivate::windowsIdToIanaIds(const QByteArray &windowsId)
806{
807 const quint16 windowsIdKey = toWindowsIdKey(windowsId);
808 QList<QByteArray> list;
809
810 for (auto data = zoneStartForWindowsId(windowsIdKey);
811 data != std::end(zoneDataTable) && data->windowsIdKey == windowsIdKey;
812 ++data) {
813 for (auto l1 : data->ids())
814 list << QByteArray(l1.data(), l1.size());
815 }
816
817 // Return the full list in alpha order
818 std::sort(list.begin(), list.end());
819 return list;
820}
821
822QList<QByteArray> QTimeZonePrivate::windowsIdToIanaIds(const QByteArray &windowsId,
823 QLocale::Territory territory)
824{
825 QList<QByteArray> list;
826 const quint16 windowsIdKey = toWindowsIdKey(windowsId);
827 const qint16 land = static_cast<quint16>(territory);
828 for (auto data = zoneStartForWindowsId(windowsIdKey);
829 data != std::end(zoneDataTable) && data->windowsIdKey == windowsIdKey;
830 ++data) {
831 // Return the region matches in preference order
832 if (data->territory == land) {
833 for (auto l1 : data->ids())
834 list << QByteArray(l1.data(), l1.size());
835 break;
836 }
837 }
838
839 return list;
840}
841
842// Define template for derived classes to reimplement so QSharedDataPointer clone() works correctly
844{
845 return d->clone();
846}
847
849{
851 while ((cut = ianaIds.indexOf(' ')) >= 0) {
852 if (id == ianaIds.first(cut))
853 return true;
854 ianaIds = ianaIds.sliced(cut + 1);
855 }
856 return id == ianaIds;
857}
858
859/*
860 UTC Offset backend.
861
862 Always present, based on UTC-offset zones.
863 Complements platform-specific backends.
864 Equivalent to Qt::OffsetFromUtc lightweight time representations.
865*/
866
867// Create default UTC time zone
869{
870 const QString name = utcQString();
871 init(utcQByteArray(), 0, name, name, QLocale::AnyTerritory, name);
872}
873
874// Create a named UTC time zone
876{
877 // Look for the name in the UTC list, if found set the values
878 for (const UtcData &data : utcDataTable) {
879 if (isEntryInIanaList(id, data.id())) {
881 init(id, data.offsetFromUtc, name, name, QLocale::AnyTerritory, name);
882 break;
883 }
884 }
885}
886
888{
889 // Convert reasonable UTC[+-]\d+(:\d+){,2} to offset in seconds.
890 // Assumption: id has already been tried as a CLDR UTC offset ID (notably
891 // including plain "UTC" itself) and a system offset ID; it's neither.
892 if (!id.startsWith("UTC") || id.size() < 5)
893 return invalidSeconds(); // Doesn't match
894 const char signChar = id.at(3);
895 if (signChar != '-' && signChar != '+')
896 return invalidSeconds(); // No sign
897 const int sign = signChar == '-' ? -1 : 1;
898
899 qint32 seconds = 0;
900 int prior = 0; // Number of fields parsed thus far
901 for (auto offset : QLatin1StringView(id.mid(4)).tokenize(':'_L1)) {
902 bool ok = false;
903 unsigned short field = offset.toUShort(&ok);
904 // Bound hour above at 24, minutes and seconds at 60:
905 if (!ok || field >= (prior ? 60 : 24))
906 return invalidSeconds();
907 seconds = seconds * 60 + field;
908 if (++prior > 3)
909 return invalidSeconds(); // Too many numbers
910 }
911
912 if (!prior)
913 return invalidSeconds(); // No numbers
914
915 while (prior++ < 3)
916 seconds *= 60;
917
918 return seconds * sign;
919}
920
921// Create from UTC offset:
923{
926 // If there's an IANA ID for this offset, use it:
927 const auto data = std::lower_bound(std::begin(utcDataTable), std::end(utcDataTable),
928 offsetSeconds, atLowerUtcOffset);
929 if (data != std::end(utcDataTable) && data->offsetFromUtc == offsetSeconds) {
930 QByteArrayView ianaId = data->id();
931 qsizetype cut = ianaId.indexOf(' ');
932 id = (cut < 0 ? ianaId : ianaId.first(cut)).toByteArray();
934 Q_ASSERT(!name.isEmpty());
935 } else { // Fall back to a UTC-offset name:
936 name = isoOffsetFormat(offsetSeconds, QTimeZone::ShortName);
937 id = name.toUtf8();
938 }
939 init(id, offsetSeconds, name, name, QLocale::AnyTerritory, name);
940}
941
943 const QString &name, const QString &abbreviation,
944 QLocale::Territory territory, const QString &comment)
945{
946 init(zoneId, offsetSeconds, name, abbreviation, territory, comment);
947}
948
950 : QTimeZonePrivate(other), m_name(other.m_name),
951 m_abbreviation(other.m_abbreviation),
952 m_comment(other.m_comment),
953 m_territory(other.m_territory),
954 m_offsetFromUtc(other.m_offsetFromUtc)
955{
956}
957
961
966
968{
969 Data d;
970 d.abbreviation = m_abbreviation;
971 d.atMSecsSinceEpoch = forMSecsSinceEpoch;
972 d.standardTimeOffset = d.offsetFromUtc = m_offsetFromUtc;
973 d.daylightTimeOffset = 0;
974 return d;
975}
976
977// Override to shortcut past base's complications:
978QTimeZonePrivate::Data QUtcTimeZonePrivate::data(QTimeZone::TimeType timeType) const
979{
980 Q_UNUSED(timeType);
982}
983
985{
986 // Officially only supports C locale names; these are surely also viable for English.
987 return locale.language() == QLocale::C || locale.language() == QLocale::English;
988}
989
990void QUtcTimeZonePrivate::init(const QByteArray &zoneId)
991{
992 m_id = zoneId;
993}
994
995void QUtcTimeZonePrivate::init(const QByteArray &zoneId, int offsetSeconds, const QString &name,
996 const QString &abbreviation, QLocale::Territory territory,
997 const QString &comment)
998{
999 m_id = zoneId;
1000 m_offsetFromUtc = offsetSeconds;
1001 m_name = name;
1002 m_abbreviation = abbreviation;
1003 m_territory = territory;
1004 m_comment = comment;
1005}
1006
1008{
1009 return m_territory;
1010}
1011
1013{
1014 return m_comment;
1015}
1016
1017// Override to bypass complications in base-class:
1019 QTimeZone::NameType nameType,
1020 const QLocale &locale) const
1021{
1022 Q_UNUSED(atMSecsSinceEpoch);
1023 return displayName(QTimeZone::StandardTime, nameType, locale);
1024}
1025
1026QString QUtcTimeZonePrivate::displayName(QTimeZone::TimeType timeType,
1027 QTimeZone::NameType nameType,
1028 const QLocale &locale) const
1029{
1030 Q_UNUSED(timeType);
1031 Q_UNUSED(locale);
1032 if (nameType == QTimeZone::ShortName)
1033 return m_abbreviation;
1034 else if (nameType == QTimeZone::OffsetName)
1035 return isoOffsetFormat(m_offsetFromUtc);
1036 return m_name;
1037}
1038
1040{
1041 Q_UNUSED(atMSecsSinceEpoch);
1042 return m_abbreviation;
1043}
1044
1046{
1047 Q_UNUSED(atMSecsSinceEpoch);
1048 return m_offsetFromUtc;
1049}
1050
1052{
1053 Q_UNUSED(atMSecsSinceEpoch);
1054 return 0;
1055}
1056
1061
1063{
1064 // Only the zone IDs supplied by CLDR and recognized by constructor.
1065 for (const UtcData &data : utcDataTable) {
1066 if (isEntryInIanaList(ianaId, data.id()))
1067 return true;
1068 }
1069 // Callers may want to || offsetFromUtcString(ianaId) != invalidSeconds(),
1070 // but those are technically not IANA IDs and the custom QTimeZone
1071 // constructor needs the return here to reflect that.
1072 return false;
1073}
1074
1076{
1077 // Only the zone IDs supplied by CLDR and recognized by constructor.
1078 QList<QByteArray> result;
1079 result.reserve(std::size(utcDataTable));
1080 for (const UtcData &data : utcDataTable) {
1081 QByteArrayView id = data.id();
1082 qsizetype cut;
1083 while ((cut = id.indexOf(' ')) >= 0) {
1084 result << id.first(cut).toByteArray();
1085 id = id.sliced(cut + 1);
1086 }
1087 result << id.toByteArray();
1088 }
1089 // Not guaranteed to be sorted, so sort:
1090 std::sort(result.begin(), result.end());
1091 // ### assuming no duplicates
1092 return result;
1093}
1094
1096{
1097 // If AnyTerritory then is request for all non-region offset codes
1098 if (country == QLocale::AnyTerritory)
1099 return availableTimeZoneIds();
1100 return QList<QByteArray>();
1101}
1102
1103QList<QByteArray> QUtcTimeZonePrivate::availableTimeZoneIds(qint32 offsetSeconds) const
1104{
1105 // Only if it's present in CLDR. (May get more than one ID: UTC, UTC+00:00
1106 // and UTC-00:00 all have the same offset.)
1107 QList<QByteArray> result;
1108 const auto data = std::lower_bound(std::begin(utcDataTable), std::end(utcDataTable),
1109 offsetSeconds, atLowerUtcOffset);
1110 if (data != std::end(utcDataTable) && data->offsetFromUtc == offsetSeconds) {
1111 QByteArrayView id = data->id();
1112 qsizetype cut;
1113 while ((cut = id.indexOf(' ')) >= 0) {
1114 result << id.first(cut).toByteArray();
1115 id = id.sliced(cut + 1);
1116 }
1117 result << id.toByteArray();
1118 }
1119 // CLDR only has round multiples of a quarter hour, and only some of
1120 // those. For anything else, throw in the ID we would use for this offset
1121 // (if we'd accept that ID).
1122 QByteArray isoName = isoOffsetFormat(offsetSeconds, QTimeZone::ShortName).toUtf8();
1123 if (offsetFromUtcString(isoName) == qint64(offsetSeconds) && !result.contains(isoName))
1124 result << isoName;
1125 // Not guaranteed to be sorted, so sort:
1126 std::sort(result.begin(), result.end());
1127 // ### assuming no duplicates
1128 return result;
1129}
1130
1131#ifndef QT_NO_DATASTREAM
1133{
1134 ds << QStringLiteral("OffsetFromUtc") << QString::fromUtf8(m_id) << m_offsetFromUtc << m_name
1135 << m_abbreviation << static_cast<qint32>(m_territory) << m_comment;
1136}
1137#endif // QT_NO_DATASTREAM
1138
QByteArray toByteArray() const
Definition qbytearray.h:796
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:611
qsizetype size() const noexcept
Returns the number of bytes in this byte array.
Definition qbytearray.h:494
iterator end()
Returns an \l{STL-style iterators}{STL-style iterator} pointing just after the last byte in the byte-...
Definition qbytearray.h:447
bool isEmpty() const noexcept
Returns true if the byte array has size 0; otherwise returns false.
Definition qbytearray.h:107
iterator begin()
Returns an \l{STL-style iterators}{STL-style iterator} pointing to the first byte in the byte-array.
Definition qbytearray.h:443
\inmodule QtCore\reentrant
Definition qdatastream.h:46
\inmodule QtCore\reentrant
Definition qdatetime.h:283
static QDateTime fromMSecsSinceEpoch(qint64 msecs, const QTimeZone &timeZone)
static qint64 currentMSecsSinceEpoch() noexcept
constexpr auto tokenize(Needle &&needle, Flags...flags) const noexcept(noexcept(qTokenize(std::declval< const QLatin1StringView & >(), std::forward< Needle >(needle), flags...))) -> decltype(qTokenize(*this, std::forward< Needle >(needle), flags...))
qsizetype size() const noexcept
Definition qlist.h:397
T & first()
Definition qlist.h:645
iterator end()
Definition qlist.h:626
iterator begin()
Definition qlist.h:625
void append(parameter_type t)
Definition qlist.h:458
Country Territory
Definition qlocale.h:861
@ AnyTerritory
Definition qlocale.h:568
static QLocale c()
Returns a QLocale object initialized to the "C" locale.
Definition qlocale.h:1146
static QLocale system()
Returns a QLocale object initialized to the system locale.
Definition qlocale.cpp:2862
@ English
Definition qlocale.h:119
\inmodule QtCore
Definition qshareddata.h:19
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
static QString fromUtf8(QByteArrayView utf8)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:6018
static QString static QString asprintf(const char *format,...) Q_ATTRIBUTE_FORMAT_PRINTF(1
Definition qstring.cpp:7263
virtual bool hasDaylightTime() const
virtual bool isDaylightTime(qint64 atMSecsSinceEpoch) const
static QByteArray utcQByteArray()
virtual QTimeZonePrivate * clone() const
static QByteArray windowsIdToDefaultIanaId(const QByteArray &windowsId)
virtual bool isDataLocale(const QLocale &locale) const
virtual Data nextTransition(qint64 afterMSecsSinceEpoch) const
virtual Data data(qint64 forMSecsSinceEpoch) const
virtual bool hasTransitions() 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
QDateTimePrivate::ZoneState stateAtZoneTime(qint64 forLocalMSecs, QDateTimePrivate::TransitionOptions resolve) 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:90
static constexpr int MinUtcOffsetSecs
Definition qtimezone.h:87
QUtcTimeZonePrivate * clone() const override
int standardTimeOffset(qint64 atMSecsSinceEpoch) const override
QList< QByteArray > availableTimeZoneIds() const override
bool isDataLocale(const QLocale &locale) const override
static qint64 offsetFromUtcString(QByteArrayView 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 displayName(qint64 atMSecsSinceEpoch, QTimeZone::NameType nameType, const QLocale &locale) const override
QString comment() 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 constexpr WindowsData windowsDataTable[]
static constexpr ZoneData zoneDataTable[]
static constexpr UtcData utcDataTable[]
@ CaseInsensitive
static jboolean cut(JNIEnv *, jobject)
#define Q_LIKELY(x)
typedef QByteArray(EGLAPIENTRYP PFNQGSGETDISPLAYSPROC)()
constexpr const T & qMin(const T &a, const T &b)
Definition qminmax.h:40
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 GLuint id
[7]
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLenum GLenum dst
GLenum GLuint GLintptr offset
GLuint name
GLint first
GLuint entry
GLuint GLsizei const GLuint const GLintptr * offsets
GLuint64EXT * result
[6]
static const QQmlJSScope * resolve(const QQmlJSScope *current, const QStringList &names)
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
QBasicUtf8StringView< false > QUtf8StringView
Definition qstringfwd.h:46
#define QStringLiteral(str)
static QT_BEGIN_NAMESPACE void init(QTextBoundaryFinder::BoundaryType type, QStringView str, QCharAttributes *attributes)
static bool earlierWindowsId(const WindowsData &entry, QByteArrayView winId) noexcept
static QList< QByteArray > selectAvailable(QList< QByteArray > &&desired, const QList< QByteArray > &all)
constexpr bool zoneAtLowerWindowsKey(const ZoneData &entry, qint16 winIdKey) noexcept
static bool isEntryInIanaList(QByteArrayView id, QByteArrayView ianaIds)
static auto zoneStartForWindowsId(quint16 windowsIdKey) noexcept
static bool earlierWinData(const WindowsData &less, const WindowsData &more) noexcept
constexpr bool atLowerUtcOffset(const UtcData &entry, qint32 offsetSeconds) noexcept
constexpr bool atLowerWindowsKey(const WindowsData &entry, qint16 winIdKey) noexcept
static quint16 toWindowsIdKey(const QByteArray &winId)
constexpr bool earlierZoneData(const ZoneData &less, const ZoneData &more) noexcept
static QByteArray toWindowsIdLiteral(quint16 windowsIdKey)
#define Q_UNUSED(x)
short qint16
Definition qtypes.h:47
unsigned short quint16
Definition qtypes.h:48
int qint32
Definition qtypes.h:49
ptrdiff_t qsizetype
Definition qtypes.h:165
long long qint64
Definition qtypes.h:60
static int sign(int x)
QList< int > list
[14]
QFuture< void > future
[5]
list indexOf("B")
QSharedPointer< T > other(t)
[5]