summaryrefslogtreecommitdiffstats
path: root/src/corelib/time/qtimezoneprivate.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/corelib/time/qtimezoneprivate.cpp')
-rw-r--r--src/corelib/time/qtimezoneprivate.cpp760
1 files changed, 408 insertions, 352 deletions
diff --git a/src/corelib/time/qtimezoneprivate.cpp b/src/corelib/time/qtimezoneprivate.cpp
index 5588cf8cce..861ebefbdf 100644
--- a/src/corelib/time/qtimezoneprivate.cpp
+++ b/src/corelib/time/qtimezoneprivate.cpp
@@ -1,137 +1,116 @@
-/****************************************************************************
-**
-** Copyright (C) 2021 The Qt Company Ltd.
-** Copyright (C) 2013 John Layt <jlayt@kde.org>
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the QtCore module of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or (at your option) the GNU General
-** Public license version 3 or any later version approved by the KDE Free
-** Qt Foundation. The licenses are as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-2.0.html and
-** https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
+// Copyright (C) 2022 The Qt Company Ltd.
+// Copyright (C) 2013 John Layt <jlayt@kde.org>
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qtimezone.h"
#include "qtimezoneprivate_p.h"
#include "qtimezoneprivate_data_p.h"
-#include <private/qnumeric_p.h>
#include <qdatastream.h>
#include <qdebug.h>
+#include <qstring.h>
+
+#include <private/qcalendarmath_p.h>
+#include <private/qnumeric_p.h>
+#include <private/qtools_p.h>
#include <algorithm>
QT_BEGIN_NAMESPACE
-/*
- Static utilities for looking up Windows ID tables
-*/
-
-static const int windowsDataTableSize = sizeof(windowsDataTable) / sizeof(QWindowsData) - 1;
-static const int zoneDataTableSize = sizeof(zoneDataTable) / sizeof(QZoneData) - 1;
-static const int utcDataTableSize = sizeof(utcDataTable) / sizeof(QUtcData) - 1;
+using namespace QtMiscUtils;
+using namespace QtTimeZoneCldr;
+using namespace Qt::StringLiterals;
-
-static const QZoneData *zoneData(quint16 index)
+// For use with std::is_sorted() in assertions:
+[[maybe_unused]]
+constexpr bool earlierZoneData(const ZoneData &less, const ZoneData &more) noexcept
{
- Q_ASSERT(index < zoneDataTableSize);
- return &zoneDataTable[index];
+ return less.windowsIdKey < more.windowsIdKey
+ || (less.windowsIdKey == more.windowsIdKey && less.territory < more.territory);
}
-static const QWindowsData *windowsData(quint16 index)
+[[maybe_unused]]
+static bool earlierWinData(const WindowsData &less, const WindowsData &more) noexcept
{
- Q_ASSERT(index < windowsDataTableSize);
- return &windowsDataTable[index];
+ // Actually only tested in the negative, to check more < less never happens,
+ // so should be true if more < less in either part; hence || not && combines.
+ return less.windowsIdKey < more.windowsIdKey
+ || less.windowsId().compare(more.windowsId(), Qt::CaseInsensitive) < 0;
}
-static const QUtcData *utcData(quint16 index)
+// For use with std::lower_bound():
+constexpr bool atLowerUtcOffset(const UtcData &entry, qint32 offsetSeconds) noexcept
{
- Q_ASSERT(index < utcDataTableSize);
- return &utcDataTable[index];
+ return entry.offsetFromUtc < offsetSeconds;
}
-// Return the Windows ID literal for a given QWindowsData
-static QByteArray windowsId(const QWindowsData *windowsData)
+constexpr bool atLowerWindowsKey(const WindowsData &entry, qint16 winIdKey) noexcept
{
- return (windowsIdData + windowsData->windowsIdIndex);
+ return entry.windowsIdKey < winIdKey;
}
-// Return the IANA ID literal for a given QWindowsData
-static QByteArray ianaId(const QWindowsData *windowsData)
+static bool earlierWindowsId(const WindowsData &entry, QByteArrayView winId) noexcept
{
- return (ianaIdData + windowsData->ianaIdIndex);
+ return entry.windowsId().compare(winId, Qt::CaseInsensitive) < 0;
}
-// Return the IANA ID literal for a given QZoneData
-static QByteArray ianaId(const QZoneData *zoneData)
+constexpr bool zoneAtLowerWindowsKey(const ZoneData &entry, qint16 winIdKey) noexcept
{
- return (ianaIdData + zoneData->ianaIdIndex);
-}
-
-static QByteArrayView ianaIdView(const QZoneData *zoneData)
-{
- return (ianaIdData + zoneData->ianaIdIndex);
-}
-
-static QByteArray utcId(const QUtcData *utcData)
-{
- return (ianaIdData + utcData->ianaIdIndex);
+ return entry.windowsIdKey < winIdKey;
}
+// Static table-lookup helpers
static quint16 toWindowsIdKey(const QByteArray &winId)
{
- for (quint16 i = 0; i < windowsDataTableSize; ++i) {
- const QWindowsData *data = windowsData(i);
- if (windowsId(data) == winId)
- return data->windowsIdKey;
- }
+ // Key and winId are monotonic, table is sorted on them.
+ const auto data = std::lower_bound(std::begin(windowsDataTable), std::end(windowsDataTable),
+ winId, earlierWindowsId);
+ if (data != std::end(windowsDataTable) && data->windowsId() == winId)
+ return data->windowsIdKey;
return 0;
}
static QByteArray toWindowsIdLiteral(quint16 windowsIdKey)
{
- for (quint16 i = 0; i < windowsDataTableSize; ++i) {
- const QWindowsData *data = windowsData(i);
- if (data->windowsIdKey == windowsIdKey)
- return windowsId(data);
+ // Caller should be passing a valid (in range) key; and table is sorted in
+ // increasing order, with no gaps in numbering, starting with key = 1 at
+ // index [0]. So this should normally work:
+ if (Q_LIKELY(windowsIdKey > 0 && windowsIdKey <= std::size(windowsDataTable))) {
+ const auto &data = windowsDataTable[windowsIdKey - 1];
+ if (Q_LIKELY(data.windowsIdKey == windowsIdKey))
+ return data.windowsId().toByteArray();
}
+ // Fall back on binary chop - key and winId are monotonic, table is sorted on them:
+ const auto data = std::lower_bound(std::begin(windowsDataTable), std::end(windowsDataTable),
+ windowsIdKey, atLowerWindowsKey);
+ if (data != std::end(windowsDataTable) && data->windowsIdKey == windowsIdKey)
+ return data->windowsId().toByteArray();
+
return QByteArray();
}
+static auto zoneStartForWindowsId(quint16 windowsIdKey) noexcept
+{
+ // Caller must check the resulting iterator isn't std::end(zoneDataTable)
+ // and does match windowsIdKey, since this is just the lower bound.
+ return std::lower_bound(std::begin(zoneDataTable), std::end(zoneDataTable),
+ windowsIdKey, zoneAtLowerWindowsKey);
+}
+
/*
- Base class implementing common utility routines, only intantiate for a null tz.
+ Base class implementing common utility routines, only instantiate for a null tz.
*/
QTimeZonePrivate::QTimeZonePrivate()
{
+ // If std::is_sorted() were constexpr, the first could be a static_assert().
+ // From C++20, we should be able to rework it in terms of std::all_of().
+ Q_ASSERT(std::is_sorted(std::begin(zoneDataTable), std::end(zoneDataTable),
+ earlierZoneData));
+ Q_ASSERT(std::is_sorted(std::begin(windowsDataTable), std::end(windowsDataTable),
+ earlierWinData));
}
QTimeZonePrivate::QTimeZonePrivate(const QTimeZonePrivate &other)
@@ -171,21 +150,17 @@ QByteArray QTimeZonePrivate::id() const
return m_id;
}
-QLocale::Country QTimeZonePrivate::country() const
+QLocale::Territory QTimeZonePrivate::territory() const
{
// Default fall-back mode, use the zoneTable to find Region of known Zones
- for (int i = 0; i < zoneDataTableSize; ++i) {
- const QZoneData *data = zoneData(i);
- QByteArrayView idView = ianaIdView(data);
- while (!idView.isEmpty()) {
- qsizetype index = idView.indexOf(' ');
- QByteArrayView next = index == -1 ? idView : idView.first(index);
- if (next == m_id)
- return (QLocale::Country)data->country;
- idView = index == -1 ? QByteArrayView() : idView.sliced(index + 1);
+ const QLatin1StringView sought(m_id.data(), m_id.size());
+ for (const ZoneData &data : zoneDataTable) {
+ for (QLatin1StringView token : data.ids()) {
+ if (token == sought)
+ return QLocale::Territory(data.territory);
}
}
- return QLocale::AnyCountry;
+ return QLocale::AnyTerritory;
}
QString QTimeZonePrivate::comment() const
@@ -218,13 +193,15 @@ QString QTimeZonePrivate::displayName(QTimeZone::TimeType timeType,
QString QTimeZonePrivate::abbreviation(qint64 atMSecsSinceEpoch) const
{
- Q_UNUSED(atMSecsSinceEpoch);
- return QString();
+ return displayName(atMSecsSinceEpoch, QTimeZone::ShortName, QLocale::c());
}
int QTimeZonePrivate::offsetFromUtc(qint64 atMSecsSinceEpoch) const
{
- return standardTimeOffset(atMSecsSinceEpoch) + daylightTimeOffset(atMSecsSinceEpoch);
+ const int std = standardTimeOffset(atMSecsSinceEpoch);
+ const int dst = daylightTimeOffset(atMSecsSinceEpoch);
+ const int bad = invalidSeconds();
+ return std == bad || dst == bad ? bad : std + dst;
}
int QTimeZonePrivate::standardTimeOffset(qint64 atMSecsSinceEpoch) const
@@ -257,42 +234,56 @@ QTimeZonePrivate::Data QTimeZonePrivate::data(qint64 forMSecsSinceEpoch) const
}
// Private only method for use by QDateTime to convert local msecs to epoch msecs
-QTimeZonePrivate::Data QTimeZonePrivate::dataForLocalTime(qint64 forLocalMSecs, int hint) const
-{
-#if !defined(Q_OS_ANDROID) || defined(Q_OS_ANDROID_EMBEDDED)
- // The Android back-end's hasDaylightTime() is only true for zones with
- // transitions in the future; we need it to mean "has ever had a transition"
- // though, so can't trust it here.
- if (!hasDaylightTime()) // No DST means same offset for all local msecs
- return data(forLocalMSecs - standardTimeOffset(forLocalMSecs) * 1000);
-#endif
+QDateTimePrivate::ZoneState QTimeZonePrivate::stateAtZoneTime(
+ qint64 forLocalMSecs, QDateTimePrivate::TransitionOptions resolve) const
+{
+ auto dataToState = [](QTimeZonePrivate::Data d) {
+ return QDateTimePrivate::ZoneState(d.atMSecsSinceEpoch + d.offsetFromUtc * 1000,
+ d.offsetFromUtc,
+ d.daylightTimeOffset ? QDateTimePrivate::DaylightTime
+ : QDateTimePrivate::StandardTime);
+ };
/*
We need a UTC time at which to ask for the offset, in order to be able to
- add that offset to forLocalMSecs, to get the UTC time we
- need. Fortunately, no time-zone offset is more than 14 hours; and DST
- transitions happen (much) more than thirty-two hours apart. So sampling
- offset sixteen hours each side gives us information we can be sure
+ add that offset to forLocalMSecs, to get the UTC time we need.
+ Fortunately, all time-zone offsets have been less than 17 hours; and DST
+ transitions happen (much) more than thirty-four hours apart. So sampling
+ offset seventeen hours each side gives us information we can be sure
brackets the correct time and at most one DST transition.
*/
- std::integral_constant<qint64, 16 * 3600 * 1000> sixteenHoursInMSecs;
- static_assert(-sixteenHoursInMSecs / 1000 < QTimeZone::MinUtcOffsetSecs
- && sixteenHoursInMSecs / 1000 > QTimeZone::MaxUtcOffsetSecs);
- using Bound = std::numeric_limits<qint64>;
+ std::integral_constant<qint64, 17 * 3600 * 1000> seventeenHoursInMSecs;
+ static_assert(-seventeenHoursInMSecs / 1000 < QTimeZone::MinUtcOffsetSecs
+ && seventeenHoursInMSecs / 1000 > QTimeZone::MaxUtcOffsetSecs);
qint64 millis;
+ // Clip the bracketing times to the bounds of the supported range.
const qint64 recent =
- sub_overflow(forLocalMSecs, sixteenHoursInMSecs, &millis)
- ? Bound::min() : millis;
+ qSubOverflow(forLocalMSecs, seventeenHoursInMSecs, &millis) || millis < minMSecs()
+ ? minMSecs() : millis; // Necessarily <= forLocalMSecs + 1.
+ // (Given that minMSecs() is std::numeric_limits<qint64>::min() + 1.)
const qint64 imminent =
- add_overflow(forLocalMSecs, sixteenHoursInMSecs, &millis)
- ? Bound::max() : millis;
- // At most one of those took the boundary value:
- Q_ASSERT(recent < imminent && sixteenHoursInMSecs < imminent - recent);
+ qAddOverflow(forLocalMSecs, seventeenHoursInMSecs, &millis)
+ ? maxMSecs() : millis; // Necessarily >= forLocalMSecs
+ // At most one of those was clipped to its boundary value:
+ Q_ASSERT(recent < imminent && seventeenHoursInMSecs < imminent - recent + 1);
+
+ const Data past = data(recent), future = data(imminent);
+ // > 99% of the time, past and future will agree:
+ if (Q_LIKELY(past.offsetFromUtc == future.offsetFromUtc
+ && past.standardTimeOffset == future.standardTimeOffset
+ // Those two imply same daylightTimeOffset.
+ && past.abbreviation == future.abbreviation)) {
+ Data data = future;
+ data.atMSecsSinceEpoch = forLocalMSecs - future.offsetFromUtc * 1000;
+ return dataToState(data);
+ }
+
/*
Offsets are Local - UTC, positive to the east of Greenwich, negative to
- the west; DST offset always exceeds standard offset, when DST applies.
+ the west; DST offset normally exceeds standard offset, when DST applies.
When we have offsets on either side of a transition, the lower one is
- standard, the higher is DST.
+ standard, the higher is DST, unless we have data telling us it's the other
+ way round.
Non-DST transitions (jurisdictions changing time-zone and time-zones
changing their standard offset, typically) are described below as if they
@@ -304,60 +295,34 @@ QTimeZonePrivate::Data QTimeZonePrivate::dataForLocalTime(qint64 forLocalMSecs,
and take the easy path; with transitions, tran and nextTran get the
correct UTC time as atMSecsSinceEpoch so comparing to nextStart selects
the right one. In all other cases, the transition changes offset and the
- reasoning that applies to DST applies just the same. Aside from hinting,
- the only thing that looks at DST-ness at all, other than inferred from
- offset changes, is the case without transition data handling an invalid
- time in the gap that a transition passed over.
-
- The handling of hint (see below) is apt to go wrong in non-DST
- transitions. There isn't really a great deal we can hope to do about that
- without adding yet more unreliable complexity to the heuristics in use for
- already obscure corner-cases.
- */
-
- /*
- The hint (really a QDateTimePrivate::DaylightStatus) is > 0 if caller
- thinks we're in DST, 0 if in standard. A value of -2 means never-DST, so
- should have been handled above; if it slips through, it's wrong but we
- should probably treat it as standard anyway (never-DST means
- always-standard, after all). If the hint turns out to be wrong, fall back
- on trying the other possibility: which makes it harmless to treat -1
- (meaning unknown) as standard (i.e. try standard first, then try DST). In
- practice, away from a transition, the only difference hint makes is to
- which candidate we try first: if the hint is wrong (or unknown and
- standard fails), we'll try the other candidate and it'll work.
-
- For the obscure (and invalid) case where forLocalMSecs falls in a
- spring-forward's missing hour, a common case is that we started with a
- date/time for which the hint was valid and adjusted it naively; for that
- case, we should correct the adjustment by shunting across the transition
- into where hint is wrong. So half-way through the gap, arrived at from
- the DST side, should be read as an hour earlier, in standard time; but, if
- arrived at from the standard side, should be read as an hour later, in
- DST. (This shall be wrong in some cases; for example, when a country
- changes its transition dates and changing a date/time by more than six
- months lands it on a transition. However, these cases are even more
- obscure than those where the heuristic is good.)
- */
-
+ reasoning that applies to DST applies just the same.
+
+ The resolution of transitions, specified by \a resolve, may be lead astray
+ if (as happens on Windows) the backend has been obliged to guess whether a
+ transition is in fact a DST one or a change to standard offset; or to
+ guess that the higher-offset side is the DST one (the reverse of this is
+ true for Ireland, using negative DST). There's not much we can do about
+ that, though.
+ */
if (hasTransitions()) {
/*
We have transitions.
- Each transition gives the offsets to use until the next; so we need the
- most recent transition before the time forLocalMSecs describes. If it
- describes a time *in* a transition, we'll need both that transition and
- the one before it. So find one transition that's probably after (and not
- much before, otherwise) and another that's definitely before, then work
- out which one to use. When both or neither work on forLocalMSecs, use
- hint to disambiguate.
+ Each transition gives the offsets to use until the next; so we need
+ the most recent transition before the time forLocalMSecs describes. If
+ it describes a time *in* a transition, we'll need both that transition
+ and the one before it. So find one transition that's probably after
+ (and not much before, otherwise) and another that's definitely before,
+ then work out which one to use. When both or neither work on
+ forLocalMSecs, use resolve to disambiguate.
*/
// Get a transition definitely before the local MSecs; usually all we need.
// Only around the transition times might we need another.
- Data tran = previousTransition(recent);
+ Data tran = past; // Data after last transition before our window.
Q_ASSERT(forLocalMSecs < 0 || // Pre-epoch TZ info may be unavailable
forLocalMSecs - tran.offsetFromUtc * 1000 >= tran.atMSecsSinceEpoch);
+ // If offset actually exceeds 17 hours, that assert may trigger.
Data nextTran = nextTransition(tran.atMSecsSinceEpoch);
/*
Now walk those forward until they bracket forLocalMSecs with transitions.
@@ -365,7 +330,7 @@ QTimeZonePrivate::Data QTimeZonePrivate::dataForLocalTime(qint64 forLocalMSecs,
One of the transitions should then be telling us the right offset to use.
In a transition, we need the transition before it (to describe the run-up
to the transition) and the transition itself; so we need to stop when
- nextTran is that transition.
+ nextTran is (invalid or) that transition.
*/
while (nextTran.atMSecsSinceEpoch != invalidMSecs()
&& forLocalMSecs > nextTran.atMSecsSinceEpoch + nextTran.offsetFromUtc * 1000) {
@@ -381,60 +346,83 @@ QTimeZonePrivate::Data QTimeZonePrivate::dataForLocalTime(qint64 forLocalMSecs,
// Check we do *really* have transitions for this zone:
if (tran.atMSecsSinceEpoch != invalidMSecs()) {
-
- /*
- So now tran is definitely before and nextTran is either after or
- only slightly before. We're going to interpret one as standard
- time, the other as DST (although the transition might in fact by a
- change in standard offset, or a chance in DST offset, e.g. to/from
- double-DST). Our hint tells us which of those to use (defaulting
- to standard if no hint): try it first; if that fails, try the
- other; if both fail, life's tricky.
- */
+ /* So now tran is definitely before ... */
Q_ASSERT(forLocalMSecs < 0
|| forLocalMSecs - tran.offsetFromUtc * 1000 > tran.atMSecsSinceEpoch);
- const qint64 nextStart = nextTran.atMSecsSinceEpoch;
- // Work out the UTC values it might make sense to return:
- nextTran.atMSecsSinceEpoch = forLocalMSecs - nextTran.offsetFromUtc * 1000;
+ // Work out the UTC value it would make sense to return if using tran:
tran.atMSecsSinceEpoch = forLocalMSecs - tran.offsetFromUtc * 1000;
-
- // If both or neither have zero DST, treat the one with lower offset as standard:
- const bool nextIsDst = !nextTran.daylightTimeOffset == !tran.daylightTimeOffset
- ? tran.offsetFromUtc < nextTran.offsetFromUtc : nextTran.daylightTimeOffset;
- // If that agrees with hint > 0, our first guess is to use nextTran; else tran.
- const bool nextFirst = nextIsDst == (hint > 0) && nextStart != invalidMSecs();
- for (int i = 0; i < 2; i++) {
- /*
- On the first pass, the case we consider is what hint told us to expect
- (except when hint was -1 and didn't actually tell us what to expect),
- so it's likely right. We only get a second pass if the first failed,
- by which time the second case, that we're trying, is likely right.
- */
- if (nextFirst ? i == 0 : i) {
- Q_ASSERT(nextStart != invalidMSecs());
- if (nextStart <= nextTran.atMSecsSinceEpoch)
- return nextTran;
- } else {
- // If next is invalid, nextFirst is false, to route us here first:
- if (nextStart == invalidMSecs() || nextStart > tran.atMSecsSinceEpoch)
- return tran;
- }
- }
+ // If we know of no transition after it, the answer is easy:
+ const qint64 nextStart = nextTran.atMSecsSinceEpoch;
+ if (nextStart == invalidMSecs())
+ return dataToState(tran); // Last valid transition.
/*
- Neither is valid (e.g. in a spring-forward's gap) and
- nextTran.atMSecsSinceEpoch < nextStart <= tran.atMSecsSinceEpoch, so
- 0 < tran.atMSecsSinceEpoch - nextTran.atMSecsSinceEpoch
- = (nextTran.offsetFromUtc - tran.offsetFromUtc) * 1000
+ ... and nextTran is either after or only slightly before. We're
+ going to interpret one as standard time, the other as DST
+ (although the transition might in fact be a change in standard
+ offset, or a change in DST offset, e.g. to/from double-DST).
+
+ Usually exactly one of those shall be relevant and we'll use it;
+ but if we're close to nextTran we may be in a transition, to be
+ settled according to resolve's rules.
*/
- int dstStep = (nextTran.offsetFromUtc - tran.offsetFromUtc) * 1000;
- Q_ASSERT(dstStep > 0); // How else could we get here ?
- if (nextFirst) { // hint thought we needed nextTran, so use tran
- tran.atMSecsSinceEpoch -= dstStep;
- return tran;
+ // Work out the UTC value it would make sense to return if using nextTran:
+ nextTran.atMSecsSinceEpoch = forLocalMSecs - nextTran.offsetFromUtc * 1000;
+
+ bool fallBack = false;
+ if (nextStart > nextTran.atMSecsSinceEpoch) {
+ // If both UTC values are before nextTran's offset applies, use tran:
+ if (nextStart > tran.atMSecsSinceEpoch)
+ return dataToState(tran);
+
+ Q_ASSERT(tran.offsetFromUtc < nextTran.offsetFromUtc);
+ // We're in a spring-forward.
+ } else if (nextStart <= tran.atMSecsSinceEpoch) {
+ // Both UTC values say we should be using nextTran:
+ return dataToState(nextTran);
+ } else {
+ Q_ASSERT(nextTran.offsetFromUtc < tran.offsetFromUtc);
+ fallBack = true; // We're in a fall-back.
}
- nextTran.atMSecsSinceEpoch += dstStep;
- return nextTran;
+ // (forLocalMSecs - nextStart) / 1000 lies between the two offsets.
+
+ // Apply resolve:
+ // Determine whether FlipForReverseDst affects the outcome:
+ const bool flipped
+ = resolve.testFlag(QDateTimePrivate::FlipForReverseDst)
+ && (fallBack ? !tran.daylightTimeOffset && nextTran.daylightTimeOffset
+ : tran.daylightTimeOffset && !nextTran.daylightTimeOffset);
+
+ if (fallBack) {
+ if (resolve.testFlag(flipped
+ ? QDateTimePrivate::FoldUseBefore
+ : QDateTimePrivate::FoldUseAfter)) {
+ return dataToState(nextTran);
+ }
+ if (resolve.testFlag(flipped
+ ? QDateTimePrivate::FoldUseAfter
+ : QDateTimePrivate::FoldUseBefore)) {
+ return dataToState(tran);
+ }
+ } else {
+ /* Neither is valid (e.g. in a spring-forward's gap) and
+ nextTran.atMSecsSinceEpoch < nextStart <= tran.atMSecsSinceEpoch.
+ So swap their atMSecsSinceEpoch to give each a moment on the
+ side of the transition that it describes, then select the one
+ after or before according to the option set:
+ */
+ std::swap(tran.atMSecsSinceEpoch, nextTran.atMSecsSinceEpoch);
+ if (resolve.testFlag(flipped
+ ? QDateTimePrivate::GapUseBefore
+ : QDateTimePrivate::GapUseAfter))
+ return dataToState(nextTran);
+ if (resolve.testFlag(flipped
+ ? QDateTimePrivate::GapUseAfter
+ : QDateTimePrivate::GapUseBefore))
+ return dataToState(tran);
+ }
+ // Reject
+ return {forLocalMSecs};
}
// Before first transition, or system has transitions but not for this zone.
// Try falling back to offsetFromUtc (works for before first transition, at least).
@@ -443,35 +431,54 @@ QTimeZonePrivate::Data QTimeZonePrivate::dataForLocalTime(qint64 forLocalMSecs,
/* Bracket and refine to discover offset. */
qint64 utcEpochMSecs;
- int early = offsetFromUtc(recent);
- int late = offsetFromUtc(imminent);
- if (early == late) { // > 99% of the time
- utcEpochMSecs = forLocalMSecs - early * 1000;
+ // We don't have true data on DST-ness, so can't apply FlipForReverseDst.
+ int early = past.offsetFromUtc;
+ int late = future.offsetFromUtc;
+ if (early == late || late == invalidSeconds()) {
+ if (early == invalidSeconds()
+ || qSubOverflow(forLocalMSecs, early * qint64(1000), &utcEpochMSecs)) {
+ return {forLocalMSecs}; // Outside representable range
+ }
} else {
- // Close to a DST transition: early > late is near a fall-back,
- // early < late is near a spring-forward.
- const int offsetInDst = qMax(early, late);
- const int offsetInStd = qMin(early, late);
// Candidate values for utcEpochMSecs (if forLocalMSecs is valid):
- const qint64 forDst = forLocalMSecs - offsetInDst * 1000;
- const qint64 forStd = forLocalMSecs - offsetInStd * 1000;
- // Best guess at the answer:
- const qint64 hinted = hint > 0 ? forDst : forStd;
- if (offsetFromUtc(hinted) == (hint > 0 ? offsetInDst : offsetInStd)) {
- utcEpochMSecs = hinted;
- } else if (hint <= 0 && offsetFromUtc(forDst) == offsetInDst) {
- utcEpochMSecs = forDst;
- } else if (hint > 0 && offsetFromUtc(forStd) == offsetInStd) {
- utcEpochMSecs = forStd;
+ const qint64 forEarly = forLocalMSecs - early * 1000;
+ const qint64 forLate = forLocalMSecs - late * 1000;
+ // If either of those doesn't have the offset we got it from, it's on
+ // the wrong side of the transition (and both may be, for a gap):
+ const bool earlyOk = offsetFromUtc(forEarly) == early;
+ const bool lateOk = offsetFromUtc(forLate) == late;
+
+ if (earlyOk) {
+ if (lateOk) {
+ Q_ASSERT(early > late);
+ // fall-back's repeated interval
+ if (resolve.testFlag(QDateTimePrivate::FoldUseBefore))
+ utcEpochMSecs = forEarly;
+ else if (resolve.testFlag(QDateTimePrivate::FoldUseAfter))
+ utcEpochMSecs = forLate;
+ else
+ return {forLocalMSecs};
+ } else {
+ // Before and clear of the transition:
+ utcEpochMSecs = forEarly;
+ }
+ } else if (lateOk) {
+ // After and clear of the transition:
+ utcEpochMSecs = forLate;
} else {
- // Invalid forLocalMSecs: in spring-forward gap.
- const int dstStep = daylightTimeOffset(early < late ? imminent : recent) * 1000;
- Q_ASSERT(dstStep); // There can't be a transition without it !
- utcEpochMSecs = (hint > 0) ? forStd - dstStep : forDst + dstStep;
+ // forLate <= gap < forEarly
+ Q_ASSERT(late > early);
+ const int dstStep = (late - early) * 1000;
+ if (resolve.testFlag(QDateTimePrivate::GapUseBefore))
+ utcEpochMSecs = forEarly - dstStep;
+ else if (resolve.testFlag(QDateTimePrivate::GapUseAfter))
+ utcEpochMSecs = forLate + dstStep;
+ else
+ return {forLocalMSecs};
}
}
- return data(utcEpochMSecs);
+ return dataToState(data(utcEpochMSecs));
}
bool QTimeZonePrivate::hasTransitions() const
@@ -514,7 +521,8 @@ QByteArray QTimeZonePrivate::systemTimeZoneId() const
bool QTimeZonePrivate::isTimeZoneIdAvailable(const QByteArray& ianaId) const
{
- // Fall-back implementation, can be made faster in subclasses
+ // Fall-back implementation, can be made faster in subclasses.
+ // Backends that don't cache the available list SHOULD override this.
const QList<QByteArray> tzIds = availableTimeZoneIds();
return std::binary_search(tzIds.begin(), tzIds.end(), ianaId);
}
@@ -524,27 +532,31 @@ QList<QByteArray> QTimeZonePrivate::availableTimeZoneIds() const
return QList<QByteArray>();
}
-QList<QByteArray> QTimeZonePrivate::availableTimeZoneIds(QLocale::Country country) const
+static QList<QByteArray> selectAvailable(QList<QByteArray>&& desired, const QList<QByteArray>& all)
+{
+ std::sort(desired.begin(), desired.end());
+ const auto newEnd = std::unique(desired.begin(), desired.end());
+ const auto newSize = std::distance(desired.begin(), newEnd);
+ QList<QByteArray> result;
+ result.reserve(qMin(all.size(), newSize));
+ std::set_intersection(all.begin(), all.end(), desired.cbegin(),
+ std::next(desired.cbegin(), newSize), std::back_inserter(result));
+ return result;
+}
+
+QList<QByteArray> QTimeZonePrivate::availableTimeZoneIds(QLocale::Territory territory) const
{
// Default fall-back mode, use the zoneTable to find Region of know Zones
QList<QByteArray> regions;
// First get all Zones in the Zones table belonging to the Region
- for (int i = 0; i < zoneDataTableSize; ++i) {
- if (zoneData(i)->country == country)
- regions += ianaId(zoneData(i)).split(' ');
+ for (const ZoneData &data : zoneDataTable) {
+ if (data.territory == territory) {
+ for (auto l1 : data.ids())
+ regions << QByteArray(l1.data(), l1.size());
+ }
}
-
- std::sort(regions.begin(), regions.end());
- regions.erase(std::unique(regions.begin(), regions.end()), regions.end());
-
- // Then select just those that are available
- const QList<QByteArray> all = availableTimeZoneIds();
- QList<QByteArray> result;
- result.reserve(qMin(all.size(), regions.size()));
- std::set_intersection(all.begin(), all.end(), regions.cbegin(), regions.cend(),
- std::back_inserter(result));
- return result;
+ return selectAvailable(std::move(regions), availableTimeZoneIds());
}
QList<QByteArray> QTimeZonePrivate::availableTimeZoneIds(int offsetFromUtc) const
@@ -552,27 +564,17 @@ QList<QByteArray> QTimeZonePrivate::availableTimeZoneIds(int offsetFromUtc) cons
// Default fall-back mode, use the zoneTable to find Offset of know Zones
QList<QByteArray> offsets;
// First get all Zones in the table using the Offset
- for (int i = 0; i < windowsDataTableSize; ++i) {
- const QWindowsData *winData = windowsData(i);
- if (winData->offsetFromUtc == offsetFromUtc) {
- for (int j = 0; j < zoneDataTableSize; ++j) {
- const QZoneData *data = zoneData(j);
- if (data->windowsIdKey == winData->windowsIdKey)
- offsets += ianaId(data).split(' ');
+ for (const WindowsData &winData : windowsDataTable) {
+ if (winData.offsetFromUtc == offsetFromUtc) {
+ for (auto data = zoneStartForWindowsId(winData.windowsIdKey);
+ data != std::end(zoneDataTable) && data->windowsIdKey == winData.windowsIdKey;
+ ++data) {
+ for (auto l1 : data->ids())
+ offsets << QByteArray(l1.data(), l1.size());
}
}
}
-
- std::sort(offsets.begin(), offsets.end());
- offsets.erase(std::unique(offsets.begin(), offsets.end()), offsets.end());
-
- // Then select just those that are available
- const QList<QByteArray> all = availableTimeZoneIds();
- QList<QByteArray> result;
- result.reserve(qMin(all.size(), offsets.size()));
- std::set_intersection(all.begin(), all.end(), offsets.cbegin(), offsets.cend(),
- std::back_inserter(result));
- return result;
+ return selectAvailable(std::move(offsets), availableTimeZoneIds());
}
#ifndef QT_NO_DATASTREAM
@@ -608,7 +610,7 @@ QTimeZone::OffsetData QTimeZonePrivate::toOffsetData(const QTimeZonePrivate::Dat
{
QTimeZone::OffsetData offsetData = invalidOffsetData();
if (data.atMSecsSinceEpoch != invalidMSecs()) {
- offsetData.atUtc = QDateTime::fromMSecsSinceEpoch(data.atMSecsSinceEpoch, Qt::UTC);
+ offsetData.atUtc = QDateTime::fromMSecsSinceEpoch(data.atMSecsSinceEpoch, QTimeZone::UTC);
offsetData.offsetFromUtc = data.offsetFromUtc;
offsetData.standardTimeOffset = data.standardTimeOffset;
offsetData.daylightTimeOffset = data.daylightTimeOffset;
@@ -621,16 +623,20 @@ QTimeZone::OffsetData QTimeZonePrivate::toOffsetData(const QTimeZonePrivate::Dat
bool QTimeZonePrivate::isValidId(const QByteArray &ianaId)
{
/*
- Main rules for defining TZ/IANA names as per ftp://ftp.iana.org/tz/code/Theory
+ Main rules for defining TZ/IANA names, as per
+ https://www.iana.org/time-zones/repository/theory.html, are:
1. Use only valid POSIX file name components
2. Within a file name component, use only ASCII letters, `.', `-' and `_'.
3. Do not use digits (except in a [+-]\d+ suffix, when used).
4. A file name component must not exceed 14 characters or start with `-'
+
However, the rules are really guidelines - a later one says
- Do not change established names if they only marginally violate the
above rules.
We may, therefore, need to be a bit slack in our check here, if we hit
- legitimate exceptions in real time-zone databases.
+ legitimate exceptions in real time-zone databases. In particular, ICU
+ includes some non-standard names with some components > 14 characters
+ long; so does Android, possibly deriving them from ICU.
In particular, aliases such as "Etc/GMT+7" and "SystemV/EST5EDT" are valid
so we need to accept digits, ':', and '+'; aliases typically have the form
@@ -657,8 +663,8 @@ bool QTimeZonePrivate::isValidId(const QByteArray &ianaId)
// Somewhat slack hand-rolled version:
const int MinSectionLength = 1;
-#if defined(Q_OS_ANDROID) && !defined(Q_OS_ANDROID_EMBEDDED)
- // Android has its own naming of zones.
+#if defined(Q_OS_ANDROID) || QT_CONFIG(icu)
+ // Android has its own naming of zones. It may well come from ICU.
// "Canada/East-Saskatchewan" has a 17-character second component.
const int MaxSectionLength = 17;
#else
@@ -674,12 +680,12 @@ bool QTimeZonePrivate::isValidId(const QByteArray &ianaId)
} else if (ch == '-') {
if (sectionLength == 0)
return false; // violates (4)
- } else if (!(ch >= 'a' && ch <= 'z')
- && !(ch >= 'A' && ch <= 'Z')
+ } else if (!isAsciiLower(ch)
+ && !isAsciiUpper(ch)
&& !(ch == '_')
&& !(ch == '.')
// Should ideally check these only happen as an offset:
- && !(ch >= '0' && ch <= '9')
+ && !isAsciiDigit(ch)
&& !(ch == '+')
&& !(ch == ':')) {
return false; // violates (2)
@@ -713,15 +719,12 @@ QString QTimeZonePrivate::isoOffsetFormat(int offsetFromUtc, QTimeZone::NameType
QByteArray QTimeZonePrivate::ianaIdToWindowsId(const QByteArray &id)
{
- for (int i = 0; i < zoneDataTableSize; ++i) {
- const QZoneData *data = zoneData(i);
- QByteArrayView idView = ianaIdView(data);
- while (!idView.isEmpty()) {
- qsizetype index = idView.indexOf(' ');
- QByteArrayView next = index == -1 ? idView : idView.first(index);
- if (next == id)
- return toWindowsIdLiteral(data->windowsIdKey);
- idView = index == -1 ? QByteArrayView() : idView.sliced(index + 1);
+ const auto idUtf8 = QUtf8StringView(id);
+
+ for (const ZoneData &data : zoneDataTable) {
+ for (auto l1 : data.ids()) {
+ if (l1 == idUtf8)
+ return toWindowsIdLiteral(data.windowsIdKey);
}
}
return QByteArray();
@@ -729,23 +732,22 @@ QByteArray QTimeZonePrivate::ianaIdToWindowsId(const QByteArray &id)
QByteArray QTimeZonePrivate::windowsIdToDefaultIanaId(const QByteArray &windowsId)
{
- const quint16 windowsIdKey = toWindowsIdKey(windowsId);
- for (int i = 0; i < windowsDataTableSize; ++i) {
- const QWindowsData *data = windowsData(i);
- if (data->windowsIdKey == windowsIdKey)
- return ianaId(data);
+ const auto data = std::lower_bound(std::begin(windowsDataTable), std::end(windowsDataTable),
+ windowsId, earlierWindowsId);
+ if (data != std::end(windowsDataTable) && data->windowsId() == windowsId) {
+ QByteArrayView id = data->ianaId();
+ if (qsizetype cut = id.indexOf(' '); cut >= 0)
+ id = id.first(cut);
+ return id.toByteArray();
}
return QByteArray();
}
QByteArray QTimeZonePrivate::windowsIdToDefaultIanaId(const QByteArray &windowsId,
- QLocale::Country country)
+ QLocale::Territory territory)
{
- const QList<QByteArray> list = windowsIdToIanaIds(windowsId, country);
- if (list.count() > 0)
- return list.first();
- else
- return QByteArray();
+ const QList<QByteArray> list = windowsIdToIanaIds(windowsId, territory);
+ return list.size() > 0 ? list.first() : QByteArray();
}
QList<QByteArray> QTimeZonePrivate::windowsIdToIanaIds(const QByteArray &windowsId)
@@ -753,10 +755,11 @@ QList<QByteArray> QTimeZonePrivate::windowsIdToIanaIds(const QByteArray &windows
const quint16 windowsIdKey = toWindowsIdKey(windowsId);
QList<QByteArray> list;
- for (int i = 0; i < zoneDataTableSize; ++i) {
- const QZoneData *data = zoneData(i);
- if (data->windowsIdKey == windowsIdKey)
- list << ianaId(data).split(' ');
+ for (auto data = zoneStartForWindowsId(windowsIdKey);
+ data != std::end(zoneDataTable) && data->windowsIdKey == windowsIdKey;
+ ++data) {
+ for (auto l1 : data->ids())
+ list << QByteArray(l1.data(), l1.size());
}
// Return the full list in alpha order
@@ -765,17 +768,23 @@ QList<QByteArray> QTimeZonePrivate::windowsIdToIanaIds(const QByteArray &windows
}
QList<QByteArray> QTimeZonePrivate::windowsIdToIanaIds(const QByteArray &windowsId,
- QLocale::Country country)
+ QLocale::Territory territory)
{
+ QList<QByteArray> list;
const quint16 windowsIdKey = toWindowsIdKey(windowsId);
- for (int i = 0; i < zoneDataTableSize; ++i) {
- const QZoneData *data = zoneData(i);
+ const qint16 land = static_cast<quint16>(territory);
+ for (auto data = zoneStartForWindowsId(windowsIdKey);
+ data != std::end(zoneDataTable) && data->windowsIdKey == windowsIdKey;
+ ++data) {
// Return the region matches in preference order
- if (data->windowsIdKey == windowsIdKey && data->country == (quint16) country)
- return ianaId(data).split(' ');
+ if (data->territory == land) {
+ for (auto l1 : data->ids())
+ list << QByteArray(l1.data(), l1.size());
+ break;
+ }
}
- return QList<QByteArray>();
+ return list;
}
// Define template for derived classes to reimplement so QSharedDataPointer clone() works correctly
@@ -784,34 +793,46 @@ template<> QTimeZonePrivate *QSharedDataPointer<QTimeZonePrivate>::clone()
return d->clone();
}
+static bool isEntryInIanaList(QByteArrayView id, QByteArrayView ianaIds)
+{
+ qsizetype cut;
+ while ((cut = ianaIds.indexOf(' ')) >= 0) {
+ if (id == ianaIds.first(cut))
+ return true;
+ ianaIds = ianaIds.sliced(cut + 1);
+ }
+ return id == ianaIds;
+}
+
/*
- UTC Offset implementation, used when QT_NO_SYSTEMLOCALE set and ICU is not being used,
- or for QDateTimes with a Qt:Spec of Qt::OffsetFromUtc.
+ UTC Offset backend.
+
+ Always present, based on UTC-offset zones.
+ Complements platform-specific backends.
+ Equivalent to Qt::OffsetFromUtc lightweight time representations.
*/
// Create default UTC time zone
QUtcTimeZonePrivate::QUtcTimeZonePrivate()
{
const QString name = utcQString();
- init(utcQByteArray(), 0, name, name, QLocale::AnyCountry, name);
+ init(utcQByteArray(), 0, name, name, QLocale::AnyTerritory, name);
}
// Create a named UTC time zone
QUtcTimeZonePrivate::QUtcTimeZonePrivate(const QByteArray &id)
{
// Look for the name in the UTC list, if found set the values
- for (int i = 0; i < utcDataTableSize; ++i) {
- const QUtcData *data = utcData(i);
- const QByteArray uid = utcId(data);
- if (uid == id) {
+ for (const UtcData &data : utcDataTable) {
+ if (isEntryInIanaList(id, data.id())) {
QString name = QString::fromUtf8(id);
- init(id, data->offsetFromUtc, name, name, QLocale::AnyCountry, name);
+ init(id, data.offsetFromUtc, name, name, QLocale::AnyTerritory, name);
break;
}
}
}
-qint64 QUtcTimeZonePrivate::offsetFromUtcString(const QByteArray &id)
+qint64 QUtcTimeZonePrivate::offsetFromUtcString(QByteArrayView id)
{
// Convert reasonable UTC[+-]\d+(:\d+){,2} to offset in seconds.
// Assumption: id has already been tried as a CLDR UTC offset ID (notably
@@ -823,46 +844,61 @@ qint64 QUtcTimeZonePrivate::offsetFromUtcString(const QByteArray &id)
return invalidSeconds(); // No sign
const int sign = signChar == '-' ? -1 : 1;
- const auto offsets = id.mid(4).split(':');
- if (offsets.isEmpty() || offsets.size() > 3)
- return invalidSeconds(); // No numbers, or too many.
-
qint32 seconds = 0;
int prior = 0; // Number of fields parsed thus far
- for (const auto &offset : offsets) {
+ for (auto offset : QLatin1StringView(id.mid(4)).tokenize(':'_L1)) {
bool ok = false;
unsigned short field = offset.toUShort(&ok);
// Bound hour above at 24, minutes and seconds at 60:
if (!ok || field >= (prior ? 60 : 24))
return invalidSeconds();
seconds = seconds * 60 + field;
- ++prior;
+ if (++prior > 3)
+ return invalidSeconds(); // Too many numbers
}
+
+ if (!prior)
+ return invalidSeconds(); // No numbers
+
while (prior++ < 3)
seconds *= 60;
return seconds * sign;
}
-// Create offset from UTC
+// Create from UTC offset:
QUtcTimeZonePrivate::QUtcTimeZonePrivate(qint32 offsetSeconds)
{
- QString utcId = isoOffsetFormat(offsetSeconds, QTimeZone::ShortName);
- init(utcId.toUtf8(), offsetSeconds, utcId, utcId, QLocale::AnyCountry, utcId);
+ QString name;
+ QByteArray id;
+ // If there's an IANA ID for this offset, use it:
+ const auto data = std::lower_bound(std::begin(utcDataTable), std::end(utcDataTable),
+ offsetSeconds, atLowerUtcOffset);
+ if (data != std::end(utcDataTable) && data->offsetFromUtc == offsetSeconds) {
+ QByteArrayView ianaId = data->id();
+ qsizetype cut = ianaId.indexOf(' ');
+ id = (cut < 0 ? ianaId : ianaId.first(cut)).toByteArray();
+ name = QString::fromUtf8(id);
+ Q_ASSERT(!name.isEmpty());
+ } else { // Fall back to a UTC-offset name:
+ name = isoOffsetFormat(offsetSeconds, QTimeZone::ShortName);
+ id = name.toUtf8();
+ }
+ init(id, offsetSeconds, name, name, QLocale::AnyTerritory, name);
}
QUtcTimeZonePrivate::QUtcTimeZonePrivate(const QByteArray &zoneId, int offsetSeconds,
const QString &name, const QString &abbreviation,
- QLocale::Country country, const QString &comment)
+ QLocale::Territory territory, const QString &comment)
{
- init(zoneId, offsetSeconds, name, abbreviation, country, comment);
+ init(zoneId, offsetSeconds, name, abbreviation, territory, comment);
}
QUtcTimeZonePrivate::QUtcTimeZonePrivate(const QUtcTimeZonePrivate &other)
: QTimeZonePrivate(other), m_name(other.m_name),
m_abbreviation(other.m_abbreviation),
m_comment(other.m_comment),
- m_country(other.m_country),
+ m_territory(other.m_territory),
m_offsetFromUtc(other.m_offsetFromUtc)
{
}
@@ -892,20 +928,20 @@ void QUtcTimeZonePrivate::init(const QByteArray &zoneId)
}
void QUtcTimeZonePrivate::init(const QByteArray &zoneId, int offsetSeconds, const QString &name,
- const QString &abbreviation, QLocale::Country country,
+ const QString &abbreviation, QLocale::Territory territory,
const QString &comment)
{
m_id = zoneId;
m_offsetFromUtc = offsetSeconds;
m_name = name;
m_abbreviation = abbreviation;
- m_country = country;
+ m_territory = territory;
m_comment = comment;
}
-QLocale::Country QUtcTimeZonePrivate::country() const
+QLocale::Territory QUtcTimeZonePrivate::territory() const
{
- return m_country;
+ return m_territory;
}
QString QUtcTimeZonePrivate::comment() const
@@ -952,12 +988,13 @@ QByteArray QUtcTimeZonePrivate::systemTimeZoneId() const
bool QUtcTimeZonePrivate::isTimeZoneIdAvailable(const QByteArray &ianaId) const
{
// Only the zone IDs supplied by CLDR and recognized by constructor.
- for (int i = 0; i < utcDataTableSize; ++i) {
- const QUtcData *data = utcData(i);
- if (utcId(data) == ianaId)
+ for (const UtcData &data : utcDataTable) {
+ if (isEntryInIanaList(ianaId, data.id()))
return true;
}
- // But see offsetFromUtcString(), which lets us accept some "unavailable" IDs.
+ // Callers may want to || offsetFromUtcString(ianaId) != invalidSeconds(),
+ // but those are technically not IANA IDs and the custom QTimeZone
+ // constructor needs the return here to reflect that.
return false;
}
@@ -965,19 +1002,26 @@ QList<QByteArray> QUtcTimeZonePrivate::availableTimeZoneIds() const
{
// Only the zone IDs supplied by CLDR and recognized by constructor.
QList<QByteArray> result;
- result.reserve(utcDataTableSize);
- for (int i = 0; i < utcDataTableSize; ++i)
- result << utcId(utcData(i));
+ result.reserve(std::size(utcDataTable));
+ for (const UtcData &data : utcDataTable) {
+ QByteArrayView id = data.id();
+ qsizetype cut;
+ while ((cut = id.indexOf(' ')) >= 0) {
+ result << id.first(cut).toByteArray();
+ id = id.sliced(cut + 1);
+ }
+ result << id.toByteArray();
+ }
// Not guaranteed to be sorted, so sort:
std::sort(result.begin(), result.end());
// ### assuming no duplicates
return result;
}
-QList<QByteArray> QUtcTimeZonePrivate::availableTimeZoneIds(QLocale::Country country) const
+QList<QByteArray> QUtcTimeZonePrivate::availableTimeZoneIds(QLocale::Territory country) const
{
- // If AnyCountry then is request for all non-region offset codes
- if (country == QLocale::AnyCountry)
+ // If AnyTerritory then is request for all non-region offset codes
+ if (country == QLocale::AnyTerritory)
return availableTimeZoneIds();
return QList<QByteArray>();
}
@@ -987,11 +1031,23 @@ QList<QByteArray> QUtcTimeZonePrivate::availableTimeZoneIds(qint32 offsetSeconds
// Only if it's present in CLDR. (May get more than one ID: UTC, UTC+00:00
// and UTC-00:00 all have the same offset.)
QList<QByteArray> result;
- for (int i = 0; i < utcDataTableSize; ++i) {
- const QUtcData *data = utcData(i);
- if (data->offsetFromUtc == offsetSeconds)
- result << utcId(data);
+ const auto data = std::lower_bound(std::begin(utcDataTable), std::end(utcDataTable),
+ offsetSeconds, atLowerUtcOffset);
+ if (data != std::end(utcDataTable) && data->offsetFromUtc == offsetSeconds) {
+ QByteArrayView id = data->id();
+ qsizetype cut;
+ while ((cut = id.indexOf(' ')) >= 0) {
+ result << id.first(cut).toByteArray();
+ id = id.sliced(cut + 1);
+ }
+ result << id.toByteArray();
}
+ // CLDR only has round multiples of a quarter hour, and only some of
+ // those. For anything else, throw in the ID we would use for this offset
+ // (if we'd accept that ID).
+ QByteArray isoName = isoOffsetFormat(offsetSeconds, QTimeZone::ShortName).toUtf8();
+ if (offsetFromUtcString(isoName) == qint64(offsetSeconds) && !result.contains(isoName))
+ result << isoName;
// Not guaranteed to be sorted, so sort:
std::sort(result.begin(), result.end());
// ### assuming no duplicates
@@ -1002,7 +1058,7 @@ QList<QByteArray> QUtcTimeZonePrivate::availableTimeZoneIds(qint32 offsetSeconds
void QUtcTimeZonePrivate::serialize(QDataStream &ds) const
{
ds << QStringLiteral("OffsetFromUtc") << QString::fromUtf8(m_id) << m_offsetFromUtc << m_name
- << m_abbreviation << (qint32) m_country << m_comment;
+ << m_abbreviation << static_cast<qint32>(m_territory) << m_comment;
}
#endif // QT_NO_DATASTREAM