Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
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
23QT_BEGIN_NAMESPACE
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(ZoneData less, 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(WindowsData less, 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(UtcData entry, qint32 offsetSeconds) noexcept
48{
49 return entry.offsetFromUtc < offsetSeconds;
50}
51
52constexpr bool atLowerWindowsKey(WindowsData entry, qint16 winIdKey) noexcept
53{
54 return entry.windowsIdKey < winIdKey;
55}
56
57static bool earlierAliasId(AliasData entry, QByteArrayView aliasId) noexcept
58{
59 return entry.aliasId().compare(aliasId, Qt::CaseInsensitive) < 0;
60}
61
62static bool earlierWindowsId(WindowsData entry, QByteArrayView winId) noexcept
63{
64 return entry.windowsId().compare(winId, Qt::CaseInsensitive) < 0;
65}
66
67constexpr bool zoneAtLowerWindowsKey(ZoneData entry, qint16 winIdKey) noexcept
68{
69 return entry.windowsIdKey < winIdKey;
70}
71
72// Static table-lookup helpers
73static quint16 toWindowsIdKey(const QByteArray &winId)
74{
75 // Key and winId are monotonic, table is sorted on them.
76 const auto data = std::lower_bound(std::begin(windowsDataTable), std::end(windowsDataTable),
77 winId, earlierWindowsId);
78 if (data != std::end(windowsDataTable) && data->windowsId() == winId)
79 return data->windowsIdKey;
80 return 0;
81}
82
83static QByteArray toWindowsIdLiteral(quint16 windowsIdKey)
84{
85 // Caller should be passing a valid (in range) key; and table is sorted in
86 // increasing order, with no gaps in numbering, starting with key = 1 at
87 // index [0]. So this should normally work:
88 if (Q_LIKELY(windowsIdKey > 0 && windowsIdKey <= std::size(windowsDataTable))) {
89 const auto &data = windowsDataTable[windowsIdKey - 1];
90 if (Q_LIKELY(data.windowsIdKey == windowsIdKey))
91 return data.windowsId().toByteArray();
92 }
93 // Fall back on binary chop - key and winId are monotonic, table is sorted on them:
94 const auto data = std::lower_bound(std::begin(windowsDataTable), std::end(windowsDataTable),
95 windowsIdKey, atLowerWindowsKey);
96 if (data != std::end(windowsDataTable) && data->windowsIdKey == windowsIdKey)
97 return data->windowsId().toByteArray();
98
99 return QByteArray();
100}
101
102static auto zoneStartForWindowsId(quint16 windowsIdKey) noexcept
103{
104 // Caller must check the resulting iterator isn't std::end(zoneDataTable)
105 // and does match windowsIdKey, since this is just the lower bound.
106 return std::lower_bound(std::begin(zoneDataTable), std::end(zoneDataTable),
107 windowsIdKey, zoneAtLowerWindowsKey);
108}
109
110/*
111 Base class implementing common utility routines, only instantiate for a null tz.
112*/
113
114QTimeZonePrivate::QTimeZonePrivate()
115{
116 // If std::is_sorted() were constexpr, the first could be a static_assert().
117 // From C++20, we should be able to rework it in terms of std::all_of().
118 Q_ASSERT(std::is_sorted(std::begin(zoneDataTable), std::end(zoneDataTable),
119 earlierZoneData));
120 Q_ASSERT(std::is_sorted(std::begin(windowsDataTable), std::end(windowsDataTable),
121 earlierWinData));
122}
123
124QTimeZonePrivate::~QTimeZonePrivate()
125{
126}
127
128bool QTimeZonePrivate::operator==(const QTimeZonePrivate &other) const
129{
130 // TODO Too simple, but need to solve problem of comparing different derived classes
131 // Should work for all System and ICU classes as names guaranteed unique, but not for Simple.
132 // Perhaps once all classes have working transitions can compare full list?
133 return (m_id == other.m_id);
134}
135
136bool QTimeZonePrivate::operator!=(const QTimeZonePrivate &other) const
137{
138 return !(*this == other);
139}
140
141bool QTimeZonePrivate::isValid() const
142{
143 return !m_id.isEmpty();
144}
145
146QByteArray QTimeZonePrivate::id() const
147{
148 return m_id;
149}
150
151QLocale::Territory QTimeZonePrivate::territory() const
152{
153 // Default fall-back mode, use the zoneTable to find Region of known Zones
154 const QLatin1StringView sought(m_id.data(), m_id.size());
155 for (const ZoneData &data : zoneDataTable) {
156 for (QLatin1StringView token : data.ids()) {
157 if (token == sought)
158 return QLocale::Territory(data.territory);
159 }
160 }
161 return QLocale::AnyTerritory;
162}
163
164QString QTimeZonePrivate::comment() const
165{
166 return QString();
167}
168
169QString QTimeZonePrivate::displayName(qint64 atMSecsSinceEpoch,
170 QTimeZone::NameType nameType,
171 const QLocale &locale) const
172{
173 const Data tran = data(atMSecsSinceEpoch);
174 if (tran.atMSecsSinceEpoch != invalidMSecs()) {
175 if (nameType == QTimeZone::OffsetName && locale.language() == QLocale::C)
176 return isoOffsetFormat(tran.offsetFromUtc);
177 if (nameType == QTimeZone::ShortName && isDataLocale(locale))
178 return tran.abbreviation;
179
180 QTimeZone::TimeType timeType
181 = tran.daylightTimeOffset != 0 ? QTimeZone::DaylightTime : QTimeZone::StandardTime;
182#if QT_CONFIG(timezone_locale)
183 return localeName(atMSecsSinceEpoch, tran.offsetFromUtc, timeType, nameType, locale);
184#else
185 return displayName(timeType, nameType, locale);
186#endif
187 }
188 return QString();
189}
190
191QString QTimeZonePrivate::displayName(QTimeZone::TimeType timeType,
192 QTimeZone::NameType nameType,
193 const QLocale &locale) const
194{
195 const Data tran = data(timeType);
196 if (tran.atMSecsSinceEpoch != invalidMSecs()) {
197 if (nameType == QTimeZone::OffsetName && isDataLocale(locale))
198 return isoOffsetFormat(tran.offsetFromUtc);
199
200#if QT_CONFIG(timezone_locale)
201 return localeName(tran.atMSecsSinceEpoch, tran.offsetFromUtc, timeType, nameType, locale);
202#endif
203 }
204 return QString();
205}
206
207QString QTimeZonePrivate::abbreviation(qint64 atMSecsSinceEpoch) const
208{
209 if (QLocale() != QLocale::c()) {
210 const QString name = displayName(atMSecsSinceEpoch, QTimeZone::ShortName, QLocale());
211 if (!name.isEmpty())
212 return name;
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
225int QTimeZonePrivate::standardTimeOffset(qint64 atMSecsSinceEpoch) const
226{
227 Q_UNUSED(atMSecsSinceEpoch);
228 return invalidSeconds();
229}
230
231int QTimeZonePrivate::daylightTimeOffset(qint64 atMSecsSinceEpoch) const
232{
233 Q_UNUSED(atMSecsSinceEpoch);
234 return invalidSeconds();
235}
236
237bool QTimeZonePrivate::hasDaylightTime() const
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 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 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
282/*!
283 \internal
284
285 Returns true if the abbreviation given in data()'s returns is appropriate
286 for use in the given \a locale.
287
288 Base implementation assumes data() corresponds to the system locale; derived
289 classes should override if their data() is something else (such as
290 C/English).
291*/
292bool QTimeZonePrivate::isDataLocale(const QLocale &locale) const
293{
294 // Guess data is for the system locale unless backend overrides that.
295 return locale == QLocale::system();
296}
297
298QTimeZonePrivate::Data QTimeZonePrivate::data(qint64 forMSecsSinceEpoch) const
299{
300 Q_UNUSED(forMSecsSinceEpoch);
301 return {};
302}
303
304// Private only method for use by QDateTime to convert local msecs to epoch msecs
305QDateTimePrivate::ZoneState QTimeZonePrivate::stateAtZoneTime(
306 qint64 forLocalMSecs, QDateTimePrivate::TransitionOptions resolve) const
307{
308 auto dataToState = [](const Data &d) {
309 return QDateTimePrivate::ZoneState(d.atMSecsSinceEpoch + d.offsetFromUtc * 1000,
310 d.offsetFromUtc,
311 d.daylightTimeOffset ? QDateTimePrivate::DaylightTime
312 : QDateTimePrivate::StandardTime);
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 if (future.atMSecsSinceEpoch == invalidMSecs()
340 && past.atMSecsSinceEpoch == invalidMSecs()) {
341 // Failed to get any useful data near this time: apparently out of range
342 // for the backend.
343 return { forLocalMSecs };
344 }
345 // > 99% of the time, past and future will agree:
346 if (Q_LIKELY(past.offsetFromUtc == future.offsetFromUtc
347 && past.standardTimeOffset == future.standardTimeOffset
348 // Those two imply same daylightTimeOffset.
349 && past.abbreviation == future.abbreviation)) {
350 Data data = future;
351 data.atMSecsSinceEpoch = forLocalMSecs - future.offsetFromUtc * 1000;
352 return dataToState(data);
353 }
354
355 /*
356 Offsets are Local - UTC, positive to the east of Greenwich, negative to
357 the west; DST offset normally exceeds standard offset, when DST applies.
358 When we have offsets on either side of a transition, the lower one is
359 standard, the higher is DST, unless we have data telling us it's the other
360 way round.
361
362 Non-DST transitions (jurisdictions changing time-zone and time-zones
363 changing their standard offset, typically) are described below as if they
364 were DST transitions (since these are more usual and familiar); the code
365 mostly concerns itself with offsets from UTC, described in terms of the
366 common case for changes in that. If there is no actual change in offset
367 (e.g. a DST transition cancelled by a standard offset change), this code
368 should handle it gracefully; without transitions, it'll see early == late
369 and take the easy path; with transitions, tran and nextTran get the
370 correct UTC time as atMSecsSinceEpoch so comparing to nextStart selects
371 the right one. In all other cases, the transition changes offset and the
372 reasoning that applies to DST applies just the same.
373
374 The resolution of transitions, specified by \a resolve, may be lead astray
375 if (as happens on Windows) the backend has been obliged to guess whether a
376 transition is in fact a DST one or a change to standard offset; or to
377 guess that the higher-offset side is the DST one (the reverse of this is
378 true for Ireland, using negative DST). There's not much we can do about
379 that, though.
380 */
381 if (hasTransitions()) {
382 /*
383 We have transitions.
384
385 Each transition gives the offsets to use until the next; so we need
386 the most recent transition before the time forLocalMSecs describes. If
387 it describes a time *in* a transition, we'll need both that transition
388 and the one before it. So find one transition that's probably after
389 (and not much before, otherwise) and another that's definitely before,
390 then work out which one to use. When both or neither work on
391 forLocalMSecs, use resolve to disambiguate.
392 */
393
394 // Get a transition definitely before the local MSecs; usually all we need.
395 // Only around the transition times might we need another.
396 Data tran = past; // Data after last transition before our window.
397 Q_ASSERT(forLocalMSecs < 0 || // Pre-epoch TZ info may be unavailable
398 forLocalMSecs - tran.offsetFromUtc * 1000 >= tran.atMSecsSinceEpoch);
399 // If offset actually exceeds 17 hours, that assert may trigger.
400 Data nextTran = nextTransition(tran.atMSecsSinceEpoch);
401 /*
402 Now walk those forward until they bracket forLocalMSecs with transitions.
403
404 One of the transitions should then be telling us the right offset to use.
405 In a transition, we need the transition before it (to describe the run-up
406 to the transition) and the transition itself; so we need to stop when
407 nextTran is (invalid or) that transition.
408 */
409 while (nextTran.atMSecsSinceEpoch != invalidMSecs()
410 && forLocalMSecs > nextTran.atMSecsSinceEpoch + nextTran.offsetFromUtc * 1000) {
411 Data newTran = nextTransition(nextTran.atMSecsSinceEpoch);
412 if (newTran.atMSecsSinceEpoch == invalidMSecs()
413 || newTran.atMSecsSinceEpoch + newTran.offsetFromUtc * 1000 > imminent) {
414 // Definitely not a relevant tansition: too far in the future.
415 break;
416 }
417 tran = nextTran;
418 nextTran = newTran;
419 }
420 const qint64 nextStart = nextTran.atMSecsSinceEpoch;
421
422 // Check we do *really* have transitions for this zone:
423 if (tran.atMSecsSinceEpoch != invalidMSecs()) {
424 /* So now tran is definitely before ... */
425 Q_ASSERT(forLocalMSecs < 0
426 || forLocalMSecs - tran.offsetFromUtc * 1000 > tran.atMSecsSinceEpoch);
427 // Work out the UTC value it would make sense to return if using tran:
428 tran.atMSecsSinceEpoch = forLocalMSecs - tran.offsetFromUtc * 1000;
429
430 // If there are no transition after it, the answer is easy - or
431 // should be - but Darwin's handling of the distant future (in macOS
432 // 15, QTBUG-126391) runs out of transitions in 506'712 CE, despite
433 // knowing about offset changes long after that. So only trust the
434 // easy answer if offsets match; otherwise, fall through to the
435 // transitions-unknown code.
436 if (nextStart == invalidMSecs() && tran.offsetFromUtc == future.offsetFromUtc)
437 return dataToState(tran); // Last valid transition.
438 }
439
440 if (tran.atMSecsSinceEpoch != invalidMSecs() && nextStart != invalidMSecs()) {
441 /*
442 ... and nextTran is either after or only slightly before. We're
443 going to interpret one as standard time, the other as DST
444 (although the transition might in fact be a change in standard
445 offset, or a change in DST offset, e.g. to/from double-DST).
446
447 Usually exactly one of those shall be relevant and we'll use it;
448 but if we're close to nextTran we may be in a transition, to be
449 settled according to resolve's rules.
450 */
451 // Work out the UTC value it would make sense to return if using nextTran:
452 nextTran.atMSecsSinceEpoch = forLocalMSecs - nextTran.offsetFromUtc * 1000;
453
454 bool fallBack = false;
455 if (nextStart > nextTran.atMSecsSinceEpoch) {
456 // If both UTC values are before nextTran's offset applies, use tran:
457 if (nextStart > tran.atMSecsSinceEpoch)
458 return dataToState(tran);
459
460 Q_ASSERT(tran.offsetFromUtc < nextTran.offsetFromUtc);
461 // We're in a spring-forward.
462 } else if (nextStart <= tran.atMSecsSinceEpoch) {
463 // Both UTC values say we should be using nextTran:
464 return dataToState(nextTran);
465 } else {
466 Q_ASSERT(nextTran.offsetFromUtc < tran.offsetFromUtc);
467 fallBack = true; // We're in a fall-back.
468 }
469 // (forLocalMSecs - nextStart) / 1000 lies between the two offsets.
470
471 // Apply resolve:
472 // Determine whether FlipForReverseDst affects the outcome:
473 const bool flipped
474 = resolve.testFlag(QDateTimePrivate::FlipForReverseDst)
475 && (fallBack ? !tran.daylightTimeOffset && nextTran.daylightTimeOffset
476 : tran.daylightTimeOffset && !nextTran.daylightTimeOffset);
477
478 if (fallBack) {
479 if (resolve.testFlag(flipped
480 ? QDateTimePrivate::FoldUseBefore
481 : QDateTimePrivate::FoldUseAfter)) {
482 return dataToState(nextTran);
483 }
484 if (resolve.testFlag(flipped
485 ? QDateTimePrivate::FoldUseAfter
486 : QDateTimePrivate::FoldUseBefore)) {
487 return dataToState(tran);
488 }
489 } else {
490 /* Neither is valid (e.g. in a spring-forward's gap) and
491 nextTran.atMSecsSinceEpoch < nextStart <= tran.atMSecsSinceEpoch.
492 So swap their atMSecsSinceEpoch to give each a moment on the
493 side of the transition that it describes, then select the one
494 after or before according to the option set:
495 */
496 std::swap(tran.atMSecsSinceEpoch, nextTran.atMSecsSinceEpoch);
497 if (resolve.testFlag(flipped
498 ? QDateTimePrivate::GapUseBefore
499 : QDateTimePrivate::GapUseAfter))
500 return dataToState(nextTran);
501 if (resolve.testFlag(flipped
502 ? QDateTimePrivate::GapUseAfter
503 : QDateTimePrivate::GapUseBefore))
504 return dataToState(tran);
505 }
506 // Reject
507 return {forLocalMSecs};
508 }
509 // Before first transition, or system has transitions but not for this zone.
510 // Try falling back to offsetFromUtc (works for before first transition, at least).
511 }
512
513 /* Bracket and refine to discover offset. */
514 qint64 utcEpochMSecs;
515
516 // We don't have true data on DST-ness, so can't apply FlipForReverseDst.
517 int early = past.offsetFromUtc;
518 int late = future.offsetFromUtc;
519 if (early == late || late == invalidSeconds()) {
520 if (early == invalidSeconds()
521 || qSubOverflow(forLocalMSecs, early * qint64(1000), &utcEpochMSecs)) {
522 return {forLocalMSecs}; // Outside representable range
523 }
524 } else {
525 // Candidate values for utcEpochMSecs (if forLocalMSecs is valid):
526 const qint64 forEarly = forLocalMSecs - early * 1000;
527 const qint64 forLate = forLocalMSecs - late * 1000;
528 // If either of those doesn't have the offset we got it from, it's on
529 // the wrong side of the transition (and both may be, for a gap):
530 const bool earlyOk = offsetFromUtc(forEarly) == early;
531 const bool lateOk = offsetFromUtc(forLate) == late;
532
533 if (earlyOk) {
534 if (lateOk) {
535 Q_ASSERT(early > late);
536 // fall-back's repeated interval
537 if (resolve.testFlag(QDateTimePrivate::FoldUseBefore))
538 utcEpochMSecs = forEarly;
539 else if (resolve.testFlag(QDateTimePrivate::FoldUseAfter))
540 utcEpochMSecs = forLate;
541 else
542 return {forLocalMSecs};
543 } else {
544 // Before and clear of the transition:
545 utcEpochMSecs = forEarly;
546 }
547 } else if (lateOk) {
548 // After and clear of the transition:
549 utcEpochMSecs = forLate;
550 } else {
551 // forLate <= gap < forEarly
552 Q_ASSERT(late > early);
553 const int dstStep = (late - early) * 1000;
554 if (resolve.testFlag(QDateTimePrivate::GapUseBefore))
555 utcEpochMSecs = forEarly - dstStep;
556 else if (resolve.testFlag(QDateTimePrivate::GapUseAfter))
557 utcEpochMSecs = forLate + dstStep;
558 else
559 return {forLocalMSecs};
560 }
561 }
562
563 return dataToState(data(utcEpochMSecs));
564}
565
566bool QTimeZonePrivate::hasTransitions() const
567{
568 return false;
569}
570
571QTimeZonePrivate::Data QTimeZonePrivate::nextTransition(qint64 afterMSecsSinceEpoch) const
572{
573 Q_UNUSED(afterMSecsSinceEpoch);
574 return {};
575}
576
577QTimeZonePrivate::Data QTimeZonePrivate::previousTransition(qint64 beforeMSecsSinceEpoch) const
578{
579 Q_UNUSED(beforeMSecsSinceEpoch);
580 return {};
581}
582
583QTimeZonePrivate::DataList QTimeZonePrivate::transitions(qint64 fromMSecsSinceEpoch,
584 qint64 toMSecsSinceEpoch) const
585{
586 DataList list;
587 if (toMSecsSinceEpoch >= fromMSecsSinceEpoch) {
588 // fromMSecsSinceEpoch is inclusive but nextTransitionTime() is exclusive so go back 1 msec
589 Data next = nextTransition(fromMSecsSinceEpoch - 1);
590 while (next.atMSecsSinceEpoch != invalidMSecs()
591 && next.atMSecsSinceEpoch <= toMSecsSinceEpoch) {
592 list.append(next);
593 next = nextTransition(next.atMSecsSinceEpoch);
594 }
595 }
596 return list;
597}
598
599QByteArray QTimeZonePrivate::systemTimeZoneId() const
600{
601 return QByteArray();
602}
603
604bool QTimeZonePrivate::isTimeZoneIdAvailable(const QByteArray &ianaId) const
605{
606 // Fall-back implementation, can be made faster in subclasses.
607 // Backends that don't cache the available list SHOULD override this.
608 const QList<QByteArray> tzIds = availableTimeZoneIds();
609 return std::binary_search(tzIds.begin(), tzIds.end(), ianaId);
610}
611
612static QList<QByteArray> selectAvailable(QList<QByteArrayView> &&desired,
613 const QList<QByteArray> &all)
614{
615 std::sort(desired.begin(), desired.end());
616 const auto newEnd = std::unique(desired.begin(), desired.end());
617 const auto newSize = std::distance(desired.begin(), newEnd);
618 QList<QByteArray> result;
619 result.reserve(qMin(all.size(), newSize));
620 std::set_intersection(all.begin(), all.end(), desired.cbegin(),
621 std::next(desired.cbegin(), newSize), std::back_inserter(result));
622 return result;
623}
624
625QList<QByteArrayView> QTimeZonePrivate::matchingTimeZoneIds(QLocale::Territory territory) const
626{
627 // Default fall-back mode: use the CLDR data to find zones for this territory.
628 QList<QByteArrayView> regions;
629#if QT_CONFIG(timezone_locale) && !QT_CONFIG(icu)
630 regions = QtTimeZoneLocale::ianaIdsForTerritory(territory);
631#endif
632 // Get all Zones in the table associated with this territory:
633 if (territory == QLocale::World) {
634 // World names are filtered out of zoneDataTable to provide the defaults
635 // in windowsDataTable.
636 for (const WindowsData &data : windowsDataTable)
637 regions << data.ianaId();
638 } else {
639 for (const ZoneData &data : zoneDataTable) {
640 if (data.territory == territory) {
641 for (auto l1 : data.ids())
642 regions << QByteArrayView(l1.data(), l1.size());
643 }
644 }
645 }
646 return regions;
647}
648
649QList<QByteArray> QTimeZonePrivate::availableTimeZoneIds(QLocale::Territory territory) const
650{
651 return selectAvailable(matchingTimeZoneIds(territory), availableTimeZoneIds());
652}
653
654QList<QByteArrayView> QTimeZonePrivate::matchingTimeZoneIds(int offsetFromUtc) const
655{
656 // Default fall-back mode: use the zoneTable to find offsets of know zones.
657 QList<QByteArrayView> offsets;
658 // First get all Zones in the table using the given offset:
659 for (const WindowsData &winData : windowsDataTable) {
660 if (winData.offsetFromUtc == offsetFromUtc) {
661 for (auto data = zoneStartForWindowsId(winData.windowsIdKey);
662 data != std::end(zoneDataTable) && data->windowsIdKey == winData.windowsIdKey;
663 ++data) {
664 for (auto l1 : data->ids())
665 offsets << QByteArrayView(l1.data(), l1.size());
666 }
667 }
668 }
669 return offsets;
670}
671
672QList<QByteArray> QTimeZonePrivate::availableTimeZoneIds(int offsetFromUtc) const
673{
674 return selectAvailable(matchingTimeZoneIds(offsetFromUtc), availableTimeZoneIds());
675}
676
677#ifndef QT_NO_DATASTREAM
678void QTimeZonePrivate::serialize(QDataStream &ds) const
679{
680 ds << QString::fromUtf8(m_id);
681}
682#endif // QT_NO_DATASTREAM
683
684// Static Utility Methods
685
686QTimeZone::OffsetData QTimeZonePrivate::invalidOffsetData()
687{
688 return { QString(), QDateTime(),
689 invalidSeconds(), invalidSeconds(), invalidSeconds() };
690}
691
692QTimeZone::OffsetData QTimeZonePrivate::toOffsetData(const QTimeZonePrivate::Data &data)
693{
694 if (data.atMSecsSinceEpoch == invalidMSecs())
695 return invalidOffsetData();
696
697 return {
698 data.abbreviation,
699 QDateTime::fromMSecsSinceEpoch(data.atMSecsSinceEpoch, QTimeZone::UTC),
700 data.offsetFromUtc, data.standardTimeOffset, data.daylightTimeOffset };
701}
702
703// Is the format of the ID valid ?
704bool QTimeZonePrivate::isValidId(const QByteArray &ianaId)
705{
706 /*
707 Main rules for defining TZ/IANA names, as per
708 https://www.iana.org/time-zones/repository/theory.html, are:
709 1. Use only valid POSIX file name components
710 2. Within a file name component, use only ASCII letters, `.', `-' and `_'.
711 3. Do not use digits (except in a [+-]\d+ suffix, when used).
712 4. A file name component must not exceed 14 characters or start with `-'
713
714 However, the rules are really guidelines - a later one says
715 - Do not change established names if they only marginally violate the
716 above rules.
717 We may, therefore, need to be a bit slack in our check here, if we hit
718 legitimate exceptions in real time-zone databases. In particular, ICU
719 includes some non-standard names with some components > 14 characters
720 long; so does Android, possibly deriving them from ICU.
721
722 In particular, aliases such as "Etc/GMT+7" and "SystemV/EST5EDT" are valid
723 so we need to accept digits, ':', and '+'; aliases typically have the form
724 of POSIX TZ strings, which allow a suffix to a proper IANA name. A POSIX
725 suffix starts with an offset (as in GMT+7) and may continue with another
726 name (as in EST5EDT, giving the DST name of the zone); a further offset is
727 allowed (for DST). The ("hard to describe and [...] error-prone in
728 practice") POSIX form even allows a suffix giving the dates (and
729 optionally times) of the annual DST transitions. Hopefully, no TZ aliases
730 go that far, but we at least need to accept an offset and (single
731 fragment) DST-name.
732
733 But for the legacy complications, the following would be preferable if
734 QRegExp would work on QByteArrays directly:
735 const QRegExp rx(QStringLiteral("[a-z+._][a-z+._-]{,13}"
736 "(?:/[a-z+._][a-z+._-]{,13})*"
737 // Optional suffix:
738 "(?:[+-]?\d{1,2}(?::\d{1,2}){,2}" // offset
739 // one name fragment (DST):
740 "(?:[a-z+._][a-z+._-]{,13})?)"),
741 Qt::CaseInsensitive);
742 return rx.exactMatch(ianaId);
743 */
744
745 // Somewhat slack hand-rolled version:
746 const int MinSectionLength = 1;
747#if defined(Q_OS_ANDROID) || QT_CONFIG(icu)
748 // Android has its own naming of zones. It may well come from ICU.
749 // "Canada/East-Saskatchewan" has a 17-character second component.
750 const int MaxSectionLength = 17;
751#else
752 const int MaxSectionLength = 14;
753#endif
754 int sectionLength = 0;
755 for (const char *it = ianaId.begin(), * const end = ianaId.end(); it != end; ++it, ++sectionLength) {
756 const char ch = *it;
757 if (ch == '/') {
758 if (sectionLength < MinSectionLength || sectionLength > MaxSectionLength)
759 return false; // violates (4)
760 sectionLength = -1;
761 } else if (ch == '-') {
762 if (sectionLength == 0)
763 return false; // violates (4)
764 } else if (!isAsciiLower(ch)
765 && !isAsciiUpper(ch)
766 && !(ch == '_')
767 && !(ch == '.')
768 // Should ideally check these only happen as an offset:
769 && !isAsciiDigit(ch)
770 && !(ch == '+')
771 && !(ch == ':')) {
772 return false; // violates (2)
773 }
774 }
775 if (sectionLength < MinSectionLength || sectionLength > MaxSectionLength)
776 return false; // violates (4)
777 return true;
778}
779
780QString QTimeZonePrivate::isoOffsetFormat(int offsetFromUtc, QTimeZone::NameType mode)
781{
782 if (mode == QTimeZone::ShortName && !offsetFromUtc)
783 return utcQString();
784
785 char sign = '+';
786 if (offsetFromUtc < 0) {
787 sign = '-';
788 offsetFromUtc = -offsetFromUtc;
789 }
790 const int secs = offsetFromUtc % 60;
791 const int mins = (offsetFromUtc / 60) % 60;
792 const int hour = offsetFromUtc / 3600;
793 QString result = QString::asprintf("UTC%c%02d", sign, hour);
794 if (mode != QTimeZone::ShortName || secs || mins)
795 result += QString::asprintf(":%02d", mins);
796 if (mode == QTimeZone::LongName || secs)
797 result += QString::asprintf(":%02d", secs);
798 return result;
799}
800
801QByteArray QTimeZonePrivate::aliasToIana(QByteArrayView alias)
802{
803 const auto data = std::lower_bound(std::begin(aliasMappingTable), std::end(aliasMappingTable),
804 alias, earlierAliasId);
805 if (data != std::end(aliasMappingTable) && data->aliasId() == alias)
806 return data->ianaId().toByteArray();
807 // Note: empty return means not an alias, which is true of an ID that others
808 // are aliases to, as the table omits self-alias entries. Let caller sort
809 // that out, rather than allocating to return alias.toByteArray().
810 return {};
811}
812
813QByteArray QTimeZonePrivate::ianaIdToWindowsId(const QByteArray &id)
814{
815 const auto idUtf8 = QUtf8StringView(id);
816
817 for (const ZoneData &data : zoneDataTable) {
818 for (auto l1 : data.ids()) {
819 if (l1 == idUtf8)
820 return toWindowsIdLiteral(data.windowsIdKey);
821 }
822 }
823 // If the IANA ID is the default for any Windows ID, it has already shown up
824 // as an ID for it in some territory; no need to search windowsDataTable[].
825 return QByteArray();
826}
827
828QByteArray QTimeZonePrivate::windowsIdToDefaultIanaId(const QByteArray &windowsId)
829{
830 const auto data = std::lower_bound(std::begin(windowsDataTable), std::end(windowsDataTable),
831 windowsId, earlierWindowsId);
832 if (data != std::end(windowsDataTable) && data->windowsId() == windowsId) {
833 QByteArrayView id = data->ianaId();
834 Q_ASSERT(id.indexOf(' ') == -1);
835 return id.toByteArray();
836 }
837 return QByteArray();
838}
839
840QByteArray QTimeZonePrivate::windowsIdToDefaultIanaId(const QByteArray &windowsId,
841 QLocale::Territory territory)
842{
843 const QList<QByteArray> list = windowsIdToIanaIds(windowsId, territory);
844 return list.size() > 0 ? list.first() : QByteArray();
845}
846
847QList<QByteArray> QTimeZonePrivate::windowsIdToIanaIds(const QByteArray &windowsId)
848{
849 const quint16 windowsIdKey = toWindowsIdKey(windowsId);
850 QList<QByteArray> list;
851
852 for (auto data = zoneStartForWindowsId(windowsIdKey);
853 data != std::end(zoneDataTable) && data->windowsIdKey == windowsIdKey;
854 ++data) {
855 for (auto l1 : data->ids())
856 list << QByteArray(l1.data(), l1.size());
857 }
858 // The default, windowsIdToDefaultIanaId(windowsId), is always an entry for
859 // at least one territory: cldr.py asserts this, in readWindowsTimeZones().
860 // So we don't need to add it here.
861
862 // Return the full list in alpha order
863 std::sort(list.begin(), list.end());
864 return list;
865}
866
867QList<QByteArray> QTimeZonePrivate::windowsIdToIanaIds(const QByteArray &windowsId,
868 QLocale::Territory territory)
869{
870 QList<QByteArray> list;
871 if (territory == QLocale::World) {
872 // World data are in windowsDataTable, not zoneDataTable.
873 list << windowsIdToDefaultIanaId(windowsId);
874 } else {
875 const quint16 windowsIdKey = toWindowsIdKey(windowsId);
876 const qint16 land = static_cast<quint16>(territory);
877 for (auto data = zoneStartForWindowsId(windowsIdKey);
878 data != std::end(zoneDataTable) && data->windowsIdKey == windowsIdKey;
879 ++data) {
880 // Return the region matches in preference order
881 if (data->territory == land) {
882 for (auto l1 : data->ids())
883 list << QByteArray(l1.data(), l1.size());
884 break;
885 }
886 }
887 }
888
889 return list;
890}
891
892// Define template for derived classes to reimplement so QSharedDataPointer clone() works correctly
893template<> QTimeZonePrivate *QSharedDataPointer<QTimeZonePrivate>::clone()
894{
895 return d->clone();
896}
897
898static bool isEntryInIanaList(QByteArrayView id, QByteArrayView ianaIds)
899{
900 qsizetype cut;
901 while ((cut = ianaIds.indexOf(' ')) >= 0) {
902 if (id == ianaIds.first(cut))
903 return true;
904 ianaIds = ianaIds.sliced(cut + 1);
905 }
906 return id == ianaIds;
907}
908
909/*
910 UTC Offset backend.
911
912 Always present, based on UTC-offset zones.
913 Complements platform-specific backends.
914 Equivalent to Qt::OffsetFromUtc lightweight time representations.
915*/
916
917// Create default UTC time zone
918QUtcTimeZonePrivate::QUtcTimeZonePrivate()
919{
920 const QString name = utcQString();
921 init(utcQByteArray(), 0, name, name, QLocale::AnyTerritory, name);
922}
923
924// Create a named UTC time zone
925QUtcTimeZonePrivate::QUtcTimeZonePrivate(const QByteArray &id)
926{
927 // Look for the name in the UTC list, if found set the values
928 for (const UtcData &data : utcDataTable) {
929 if (isEntryInIanaList(id, data.id())) {
930 QString name = QString::fromUtf8(id);
931 init(id, data.offsetFromUtc, name, name, QLocale::AnyTerritory, name);
932 break;
933 }
934 }
935}
936
937qint64 QUtcTimeZonePrivate::offsetFromUtcString(QByteArrayView id)
938{
939 // Convert reasonable UTC[+-]\d+(:\d+){,2} to offset in seconds.
940 // Assumption: id has already been tried as a CLDR UTC offset ID (notably
941 // including plain "UTC" itself) and a system offset ID; it's neither.
942 if (!id.startsWith("UTC") || id.size() < 5)
943 return invalidSeconds(); // Doesn't match
944 const char signChar = id.at(3);
945 if (signChar != '-' && signChar != '+')
946 return invalidSeconds(); // No sign
947 const int sign = signChar == '-' ? -1 : 1;
948
949 qint32 seconds = 0;
950 int prior = 0; // Number of fields parsed thus far
951 for (auto offset : QLatin1StringView(id.mid(4)).tokenize(':'_L1)) {
952 bool ok = false;
953 unsigned short field = offset.toUShort(&ok);
954 // Bound hour above at 24, minutes and seconds at 60:
955 if (!ok || field >= (prior ? 60 : 24))
956 return invalidSeconds();
957 seconds = seconds * 60 + field;
958 if (++prior > 3)
959 return invalidSeconds(); // Too many numbers
960 }
961
962 if (!prior)
963 return invalidSeconds(); // No numbers
964
965 while (prior++ < 3)
966 seconds *= 60;
967
968 return seconds * sign;
969}
970
971// Create from UTC offset:
972QUtcTimeZonePrivate::QUtcTimeZonePrivate(qint32 offsetSeconds)
973{
974 QString name;
975 QByteArray id;
976 // If there's an IANA ID for this offset, use it:
977 const auto data = std::lower_bound(std::begin(utcDataTable), std::end(utcDataTable),
978 offsetSeconds, atLowerUtcOffset);
979 if (data != std::end(utcDataTable) && data->offsetFromUtc == offsetSeconds) {
980 QByteArrayView ianaId = data->id();
981 qsizetype cut = ianaId.indexOf(' ');
982 QByteArrayView cutId = (cut < 0 ? ianaId : ianaId.first(cut));
983 if (cutId == utcQByteArray()) {
984 // optimize: reuse interned strings for the common case
985 id = utcQByteArray();
986 name = utcQString();
987 } else {
988 // fallback to allocate new strings otherwise
989 id = cutId.toByteArray();
990 name = QString::fromUtf8(id);
991 }
992 Q_ASSERT(!name.isEmpty());
993 } else { // Fall back to a UTC-offset name:
994 name = isoOffsetFormat(offsetSeconds, QTimeZone::ShortName);
995 id = name.toUtf8();
996 }
997 init(id, offsetSeconds, name, name, QLocale::AnyTerritory, name);
998}
999
1000QUtcTimeZonePrivate::QUtcTimeZonePrivate(const QByteArray &zoneId, int offsetSeconds,
1001 const QString &name, const QString &abbreviation,
1002 QLocale::Territory territory, const QString &comment)
1003{
1004 init(zoneId, offsetSeconds, name, abbreviation, territory, comment);
1005}
1006
1007QUtcTimeZonePrivate::QUtcTimeZonePrivate(const QUtcTimeZonePrivate &other)
1008 : QTimeZonePrivate(other), m_name(other.m_name),
1009 m_abbreviation(other.m_abbreviation),
1010 m_comment(other.m_comment),
1011 m_territory(other.m_territory),
1012 m_offsetFromUtc(other.m_offsetFromUtc)
1013{
1014}
1015
1016QUtcTimeZonePrivate::~QUtcTimeZonePrivate()
1017{
1018}
1019
1020QUtcTimeZonePrivate *QUtcTimeZonePrivate::clone() const
1021{
1022 return new QUtcTimeZonePrivate(*this);
1023}
1024
1025QTimeZonePrivate::Data QUtcTimeZonePrivate::data(qint64 forMSecsSinceEpoch) const
1026{
1027 Data d;
1028 d.abbreviation = m_abbreviation;
1029 d.atMSecsSinceEpoch = forMSecsSinceEpoch;
1030 d.standardTimeOffset = d.offsetFromUtc = m_offsetFromUtc;
1031 d.daylightTimeOffset = 0;
1032 return d;
1033}
1034
1035// Override to shortcut past base's complications:
1036QTimeZonePrivate::Data QUtcTimeZonePrivate::data(QTimeZone::TimeType timeType) const
1037{
1038 Q_UNUSED(timeType);
1039 return data(QDateTime::currentMSecsSinceEpoch());
1040}
1041
1042bool QUtcTimeZonePrivate::isDataLocale(const QLocale &locale) const
1043{
1044 // Officially only supports C locale names; these are surely also viable for English.
1045 return locale.language() == QLocale::C || locale.language() == QLocale::English;
1046}
1047
1048void QUtcTimeZonePrivate::init(const QByteArray &zoneId, int offsetSeconds, const QString &name,
1049 const QString &abbreviation, QLocale::Territory territory,
1050 const QString &comment)
1051{
1052 m_id = zoneId;
1053 m_offsetFromUtc = offsetSeconds;
1054 m_name = name;
1055 m_abbreviation = abbreviation;
1056 m_territory = territory;
1057 m_comment = comment;
1058}
1059
1060QLocale::Territory QUtcTimeZonePrivate::territory() const
1061{
1062 return m_territory;
1063}
1064
1065QString QUtcTimeZonePrivate::comment() const
1066{
1067 return m_comment;
1068}
1069
1070// Override to bypass complications in base-class:
1071QString QUtcTimeZonePrivate::displayName(qint64 atMSecsSinceEpoch,
1072 QTimeZone::NameType nameType,
1073 const QLocale &locale) const
1074{
1075 Q_UNUSED(atMSecsSinceEpoch);
1076 return displayName(QTimeZone::StandardTime, nameType, locale);
1077}
1078
1079QString QUtcTimeZonePrivate::displayName(QTimeZone::TimeType timeType,
1080 QTimeZone::NameType nameType,
1081 const QLocale &locale) const
1082{
1083 Q_UNUSED(timeType);
1084 Q_UNUSED(locale);
1085 if (nameType == QTimeZone::ShortName)
1086 return m_abbreviation;
1087 else if (nameType == QTimeZone::OffsetName)
1088 return isoOffsetFormat(m_offsetFromUtc);
1089 return m_name;
1090}
1091
1092QString QUtcTimeZonePrivate::abbreviation(qint64 atMSecsSinceEpoch) const
1093{
1094 Q_UNUSED(atMSecsSinceEpoch);
1095 return m_abbreviation;
1096}
1097
1098qint32 QUtcTimeZonePrivate::standardTimeOffset(qint64 atMSecsSinceEpoch) const
1099{
1100 Q_UNUSED(atMSecsSinceEpoch);
1101 return m_offsetFromUtc;
1102}
1103
1104qint32 QUtcTimeZonePrivate::daylightTimeOffset(qint64 atMSecsSinceEpoch) const
1105{
1106 Q_UNUSED(atMSecsSinceEpoch);
1107 return 0;
1108}
1109
1110QByteArray QUtcTimeZonePrivate::systemTimeZoneId() const
1111{
1112 return utcQByteArray();
1113}
1114
1115bool QUtcTimeZonePrivate::isTimeZoneIdAvailable(const QByteArray &ianaId) const
1116{
1117 // Only the zone IDs supplied by CLDR and recognized by constructor.
1118 for (const UtcData &data : utcDataTable) {
1119 if (isEntryInIanaList(ianaId, data.id()))
1120 return true;
1121 }
1122 // Callers may want to || offsetFromUtcString(ianaId) != invalidSeconds(),
1123 // but those are technically not IANA IDs and the custom QTimeZone
1124 // constructor needs the return here to reflect that.
1125 return false;
1126}
1127
1128QList<QByteArray> QUtcTimeZonePrivate::availableTimeZoneIds() const
1129{
1130 // Only the zone IDs supplied by CLDR and recognized by constructor.
1131 QList<QByteArray> result;
1132 result.reserve(std::size(utcDataTable));
1133 for (const UtcData &data : utcDataTable) {
1134 QByteArrayView id = data.id();
1135 qsizetype cut;
1136 while ((cut = id.indexOf(' ')) >= 0) {
1137 result << id.first(cut).toByteArray();
1138 id = id.sliced(cut + 1);
1139 }
1140 result << id.toByteArray();
1141 }
1142 // Not guaranteed to be sorted, so sort:
1143 std::sort(result.begin(), result.end());
1144 // ### assuming no duplicates
1145 return result;
1146}
1147
1148QList<QByteArray> QUtcTimeZonePrivate::availableTimeZoneIds(QLocale::Territory country) const
1149{
1150 // If AnyTerritory then is request for all non-region offset codes
1151 if (country == QLocale::AnyTerritory)
1152 return availableTimeZoneIds();
1153 return QList<QByteArray>();
1154}
1155
1156QList<QByteArray> QUtcTimeZonePrivate::availableTimeZoneIds(qint32 offsetSeconds) const
1157{
1158 // Only if it's present in CLDR. (May get more than one ID: UTC, UTC+00:00
1159 // and UTC-00:00 all have the same offset.)
1160 QList<QByteArray> result;
1161 const auto data = std::lower_bound(std::begin(utcDataTable), std::end(utcDataTable),
1162 offsetSeconds, atLowerUtcOffset);
1163 if (data != std::end(utcDataTable) && data->offsetFromUtc == offsetSeconds) {
1164 QByteArrayView id = data->id();
1165 qsizetype cut;
1166 while ((cut = id.indexOf(' ')) >= 0) {
1167 result << id.first(cut).toByteArray();
1168 id = id.sliced(cut + 1);
1169 }
1170 result << id.toByteArray();
1171 }
1172 // CLDR only has round multiples of a quarter hour, and only some of
1173 // those. For anything else, throw in the ID we would use for this offset
1174 // (if we'd accept that ID).
1175 QByteArray isoName = isoOffsetFormat(offsetSeconds, QTimeZone::ShortName).toUtf8();
1176 if (offsetFromUtcString(isoName) == qint64(offsetSeconds) && !result.contains(isoName))
1177 result << isoName;
1178 // Not guaranteed to be sorted, so sort:
1179 std::sort(result.begin(), result.end());
1180 // ### assuming no duplicates
1181 return result;
1182}
1183
1184#ifndef QT_NO_DATASTREAM
1185void QUtcTimeZonePrivate::serialize(QDataStream &ds) const
1186{
1187 ds << QStringLiteral("OffsetFromUtc") << QString::fromUtf8(m_id) << m_offsetFromUtc << m_name
1188 << m_abbreviation << static_cast<qint32>(m_territory) << m_comment;
1189}
1190#endif // QT_NO_DATASTREAM
1191
1192QT_END_NAMESPACE
Definition qlist.h:80
static constexpr WindowsData windowsDataTable[]
static constexpr ZoneData zoneDataTable[]
Definition qcompare.h:76
constexpr bool atLowerWindowsKey(WindowsData entry, qint16 winIdKey) noexcept
static bool earlierAliasId(AliasData entry, QByteArrayView aliasId) noexcept
static bool isEntryInIanaList(QByteArrayView id, QByteArrayView ianaIds)
static bool earlierWinData(WindowsData less, WindowsData more) noexcept
static auto zoneStartForWindowsId(quint16 windowsIdKey) noexcept
constexpr bool zoneAtLowerWindowsKey(ZoneData entry, qint16 winIdKey) noexcept
static QList< QByteArray > selectAvailable(QList< QByteArrayView > &&desired, const QList< QByteArray > &all)
static quint16 toWindowsIdKey(const QByteArray &winId)
static QByteArray toWindowsIdLiteral(quint16 windowsIdKey)
constexpr bool atLowerUtcOffset(UtcData entry, qint32 offsetSeconds) noexcept
constexpr bool earlierZoneData(ZoneData less, ZoneData more) noexcept
static bool earlierWindowsId(WindowsData entry, QByteArrayView winId) noexcept