5#include <private/qtimezonelocale_p.h>
6#include <private/qtimezoneprivate_p.h>
9# include <QtCore/qspan.h>
10# include <private/qdatetime_p.h>
11# include <private/qtools_p.h>
13# include "qtimezonelocale_data_p.h"
14# include "qtimezoneprivate_data_p.h"
15# ifdef QT_CLDR_ZONE_DEBUG
16# include "../text/qlocale_data_p.h"
18static_assert(std::size(locale_data) == std::size(QtTimeZoneLocale::localeZoneData));
21 for (std::size_t i = 0; i < std::size(locale_data); ++i) {
22 const auto &loc = locale_data[i];
23 const auto &zone = QtTimeZoneLocale::localeZoneData[i];
24 if (loc.m_language_id != zone.m_language_id
25 || loc.m_script_id != zone.m_script_id
26 || loc.m_territory_id != zone.m_territory_id) {
38using namespace Qt::StringLiterals;
44UCalendarDisplayNameType ucalDisplayNameType(QTimeZone::TimeType timeType,
45 QTimeZone::NameType nameType)
50 case QTimeZone::ShortName:
51 return timeType == QTimeZone::DaylightTime ? UCAL_SHORT_DST : UCAL_SHORT_STANDARD;
52 case QTimeZone::DefaultName:
53 case QTimeZone::LongName:
54 return timeType == QTimeZone::DaylightTime ? UCAL_DST : UCAL_STANDARD;
55 case QTimeZone::OffsetName:
58 Q_UNREACHABLE_RETURN(UCAL_STANDARD);
63namespace QtTimeZoneLocale {
67QString ucalTimeZoneDisplayName(UCalendar *ucal,
68 QTimeZone::TimeType timeType,
69 QTimeZone::NameType nameType,
70 const QByteArray &localeCode)
72 constexpr int32_t BigNameLength = 50;
73 int32_t size = BigNameLength;
74 QString result(size, Qt::Uninitialized);
75 auto dst = [&result]() {
return reinterpret_cast<UChar *>(result.data()); };
76 UErrorCode status = U_ZERO_ERROR;
77 const UCalendarDisplayNameType utype = ucalDisplayNameType(timeType, nameType);
80 size = ucal_getTimeZoneDisplayName(ucal, utype, localeCode.constData(),
81 dst(), size, &status);
84 if (size > BigNameLength || status == U_BUFFER_OVERFLOW_ERROR) {
86 status = U_ZERO_ERROR;
87 size = ucal_getTimeZoneDisplayName(ucal, utype, localeCode.constData(),
88 dst(), size, &status);
91 if (!U_SUCCESS(status))
99bool ucalKnownTimeZoneId(
const QString &ianaStr)
101 const UChar *
const name =
reinterpret_cast<
const UChar *>(ianaStr.constData());
104 constexpr size_t size = 64;
109 UErrorCode status = U_ZERO_ERROR;
112 ucal_getCanonicalTimeZoneID(name, ianaStr.size(), buffer, size, &isSys, &status);
120QString QTimeZonePrivate::localeName(qint64 atMSecsSinceEpoch,
int offsetFromUtc,
121 QTimeZone::TimeType timeType,
122 QTimeZone::NameType nameType,
123 const QLocale &locale)
const
125 Q_UNUSED(atMSecsSinceEpoch);
129 if (nameType == QTimeZone::OffsetName)
130 return isoOffsetFormat(offsetFromUtc);
132 const QString id = QString::fromUtf8(m_id);
135 if (!QtTimeZoneLocale::ucalKnownTimeZoneId(id))
138 const QByteArray loc = locale.name().toUtf8();
139 UErrorCode status = U_ZERO_ERROR;
141 UCalendar *ucal = ucal_open(
reinterpret_cast<
const UChar *>(id.data()), id.size(),
142 loc.constData(), UCAL_DEFAULT, &status);
143 if (ucal && U_SUCCESS(status)) {
144 auto tidier = qScopeGuard([ucal]() { ucal_close(ucal); });
145 return QtTimeZoneLocale::ucalTimeZoneDisplayName(ucal, timeType, nameType, loc);
150namespace QtTimeZoneLocale {
152using namespace QtTimeZoneCldr;
153constexpr QByteArrayView LocaleZoneExemplar::ianaId()
const {
return ianaIdData + ianaIdIndex; }
154constexpr QByteArrayView LocaleZoneNames::ianaId()
const {
return ianaIdData + ianaIdIndex; }
158using namespace QtTimeZoneLocale;
159using namespace QtTimeZoneCldr;
162template <
typename Row,
typename Sought,
typename Condition>
163const Row *findTableEntryFor(
const QSpan<Row> data, Sought value, Condition test)
168 auto begin = data.begin(), end = data.end();
169 Q_ASSERT(begin == end || end->localeIndex > begin->localeIndex);
170 Q_ASSERT(begin == end || end[-1].localeIndex == begin->localeIndex);
171 auto row = std::lower_bound(begin, end, value, test);
172 return row == end ?
nullptr : row;
175QString exemplarCityFor(
const LocaleZoneData &locale,
const LocaleZoneData &next,
178 auto xct = findTableEntryFor(
179 QSpan(localeZoneExemplarTable).first(next.m_exemplarTableStart
180 ).sliced(locale.m_exemplarTableStart),
181 iana, [](
auto &row, QByteArrayView key) {
return row.ianaId() < key; });
182 if (xct && xct->ianaId() == iana)
183 return xct->exemplarCity().getData(exemplarCityTable);
188quint32 clipEpochMinute(qint64 epochMinute)
192 constexpr quint32 epoch = 0;
201 constexpr quint32 ragnarok = 1 ^ ~epoch;
202 return epochMinute + 1 >= ragnarok ? ragnarok : quint32(epochMinute);
205constexpr bool intervalEndsBefore(
const ZoneMetaHistory &record, quint32 dt)
noexcept
208 return record.end <= dt;
212
213
214
215quint16 metaZoneAt(QByteArrayView zoneId, qint64 atMSecsSinceEpoch)
217 using namespace QtPrivate::DateTimeConstants;
218 auto it = std::lower_bound(std::begin(zoneHistoryTable), std::end(zoneHistoryTable), zoneId,
219 [](
const ZoneMetaHistory &record, QByteArrayView id) {
220 return record.ianaId().compare(id, Qt::CaseInsensitive) < 0;
222 if (it == std::end(zoneHistoryTable) || it->ianaId().compare(zoneId, Qt::CaseInsensitive) > 0)
225 std::upper_bound(it, std::end(zoneHistoryTable), zoneId,
226 [](QByteArrayView id,
const ZoneMetaHistory &record) {
227 return id.compare(record.ianaId(), Qt::CaseInsensitive) < 0;
229 const quint32 dt = clipEpochMinute(atMSecsSinceEpoch / MSECS_PER_MIN);
230 it = std::lower_bound(it, stop, dt, intervalEndsBefore);
231 return it != stop && it->begin <= dt ? it->metaZoneKey : 0;
235bool zoneEverInMeta(QByteArrayView zoneId, quint16 metaKey)
237 for (
auto it = std::lower_bound(std::begin(zoneHistoryTable), std::end(zoneHistoryTable),
239 [](
const ZoneMetaHistory &record, QByteArrayView id) {
240 return record.ianaId().compare(id, Qt::CaseInsensitive) < 0;
242 it != std::end(zoneHistoryTable) && it->ianaId().compare(zoneId, Qt::CaseInsensitive) == 0;
244 if (it->metaZoneKey == metaKey)
250constexpr bool dataBeforeMeta(
const MetaZoneData &row, quint16 metaKey)
noexcept
252 return row.metaZoneKey < metaKey;
255constexpr bool metaDataBeforeTerritory(
const MetaZoneData &row, qint16 territory)
noexcept
257 return row.territory < territory;
260const MetaZoneData *metaZoneStart(quint16 metaKey)
262 const MetaZoneData *
const from =
263 std::lower_bound(std::begin(metaZoneTable), std::end(metaZoneTable),
264 metaKey, dataBeforeMeta);
265 if (from == std::end(metaZoneTable) || from->metaZoneKey != metaKey) {
266 qWarning(
"No metazone data found for metazone key %d", metaKey);
272const MetaZoneData *metaZoneDataFor(
const MetaZoneData *from, QLocale::Territory territory)
274 const quint16 metaKey = from->metaZoneKey;
275 const MetaZoneData *
const end =
276 std::lower_bound(from, std::end(metaZoneTable), metaKey + 1, dataBeforeMeta);
277 Q_ASSERT(end != from && end[-1].metaZoneKey == metaKey);
278 QLocale::Territory land = territory;
280 const MetaZoneData *row =
281 std::lower_bound(from, end, qint16(land), metaDataBeforeTerritory);
282 if (row != end && QLocale::Territory(row->territory) == land) {
283 Q_ASSERT(row->metaZoneKey == metaKey);
287 }
while (std::exchange(land, QLocale::World) != QLocale::World);
289 qWarning(
"Metazone %s lacks World data for %ls",
290 from->metaZoneId().constData(),
291 qUtf16Printable(QLocale::territoryToString(territory)));
295QString addPadded(qsizetype width,
const QString &zero,
const QString &number, QString &&onto)
298 width -= number.size() / zero.size();
303 return std::move(onto) + number;
306QString formatOffset(QStringView format,
int offsetMinutes,
const QLocale &locale,
307 QLocale::FormatType form)
309 Q_ASSERT(offsetMinutes >= 0);
310 const QString hour = locale.toString(offsetMinutes / 60);
311 const QString mins = locale.toString(offsetMinutes % 60);
314 const QString zero = locale.zeroDigit();
315 QStringView tail = format;
317 while (!tail.isEmpty()) {
318 if (tail.startsWith(u'\'')) {
319 qsizetype end = tail.indexOf(u'\'', 1);
321 qWarning(
"Unbalanced quote in offset format string: %s",
322 format.toUtf8().constData());
323 return result + tail;
324 }
else if (end == 1) {
327 tail = tail.sliced(2);
330 while (end + 1 < tail.size() && tail[end + 1] == u'\'') {
333 result += tail.sliced(1, end);
334 tail = tail.sliced(end + 1);
335 end = tail.indexOf(u'\'', 1);
337 qWarning(
"Unbalanced quoted quote in offset format string: %s",
338 format.toUtf8().constData());
339 return result + tail;
344 result += tail.sliced(1, end - 1);
345 tail = tail.sliced(end + 1);
347 }
else if (tail.startsWith(u'H')) {
349 while (width < tail.size() && tail[width] == u'H')
351 tail = tail.sliced(width);
352 if (form != QLocale::NarrowFormat)
353 result = addPadded(width, zero, hour, std::move(result));
356 }
else if (tail.startsWith(u'm')) {
358 while (width < tail.size() && tail[width] == u'm')
362 tail = tail.sliced(width);
363 if (form != QLocale::NarrowFormat)
364 result = addPadded(width, zero, mins, std::move(result));
365 else if (offsetMinutes % 60)
367 else if (result.endsWith(u':') || result.endsWith(u'.'))
370 }
else if (tail[0].isHighSurrogate() && tail.size() > 1
371 && tail[1].isLowSurrogate()) {
372 result += tail.first(2);
373 tail = tail.sliced(2);
375 result += tail.front();
376 tail = tail.sliced(1);
382struct OffsetFormatMatch
386 operator
bool() {
return size > 0; }
389OffsetFormatMatch matchOffsetText(QStringView text, QStringView format,
const QLocale &locale,
390 QLocale::FormatType scale)
395 OffsetFormatMatch res;
401 qsizetype cut = format.indexOf(u'H');
402 if (cut < 0 || !text.startsWith(format.first(cut)) || !format.endsWith(u"mm"))
404 text = text.sliced(cut);
405 QStringView sep = format.sliced(cut).chopped(2);
407 while (hlen < sep.size() && sep[hlen] == u'H')
409 sep = sep.sliced(hlen);
412 while (digits < text.size() && digits < 4 && text[digits].isDigit())
420 if (scale == QLocale::NarrowFormat || (hlen < 2 && text[0] != u'0'))
422 else if (digits < hlen + 2)
424 minStr = text.sliced(hlen).first(2);
425 }
else if (scale == QLocale::NarrowFormat) {
427 }
else if (hlen != digits) {
431 const qsizetype sepAt = text.indexOf(sep);
434 if (scale == QLocale::NarrowFormat || (hlen < 2 && text[0] != u'0'))
436 else if (digits != hlen)
438 if (sepAt >= 0 && text.size() >= sepAt + sep.size() + 2)
439 minStr = text.sliced(sepAt + sep.size()).first(2);
440 else if (scale != QLocale::NarrowFormat)
443 minStr = text.sliced(sepAt + sep.size());
449 uint minute = minStr.isEmpty() ? 0 : locale.toUInt(minStr, &ok);
450 if (!ok && scale == QLocale::NarrowFormat) {
455 if (ok && minute < 60) {
456 uint hour = locale.toUInt(text.first(hlen), &ok);
458 res.offset = (hour * 60 + minute) * 60;
459 res.size = cut + hlen;
460 if (!minStr.isEmpty())
461 res.size += sep.size() + minStr.size();
467OffsetFormatMatch matchOffsetFormat(QStringView text,
const QLocale &locale, qsizetype locInd,
468 QLocale::FormatType scale)
470 const LocaleZoneData &locData = localeZoneData[locInd];
471 const QStringView posHourForm = locData.posHourFormat().viewData(hourFormatTable);
472 const QStringView negHourForm = locData.negHourFormat().viewData(hourFormatTable);
474 const bool mapNeg = text.contains(u'-')
475 && (negHourForm.contains(u'\u2212') || negHourForm.contains(locale.negativeSign()));
477 if (scale == QLocale::ShortFormat) {
478 if (
auto match = matchOffsetText(text, posHourForm, locale, scale))
480 if (
auto match = matchOffsetText(text, negHourForm, locale, scale)) {
481 return { match.size, -match.offset };
483 const QString mapped = negHourForm.toString()
484 .replace(u'\u2212', u'-').replace(locale.negativeSign(),
"-"_L1);
485 if (
auto match = matchOffsetText(text, mapped, locale, scale))
486 return { match.size, -match.offset };
489 const QStringView offsetFormat = locData.offsetGmtFormat().viewData(gmtFormatTable);
490 qsizetype cut = offsetFormat.indexOf(u"%0");
492 const QStringView gmtPrefix = offsetFormat.first(cut);
493 const QStringView gmtSuffix = offsetFormat.sliced(cut + 2);
494 const qsizetype gmtSize = cut + gmtSuffix.size();
497 if ((gmtPrefix.isEmpty() || text.startsWith(gmtPrefix))
498 && (gmtSuffix.isEmpty() || text.sliced(cut).indexOf(gmtSuffix) >= 0)) {
499 if (
auto match = matchOffsetText(text.sliced(cut), posHourForm, locale, scale)) {
500 if (text.sliced(cut + match.size).startsWith(gmtSuffix))
501 return { gmtSize + match.size, match.offset };
503 if (
auto match = matchOffsetText(text.sliced(cut), negHourForm, locale, scale)) {
504 if (text.sliced(cut + match.size).startsWith(gmtSuffix))
505 return { gmtSize + match.size, -match.offset };
507 const QString mapped = negHourForm.toString()
508 .replace(u'\u2212', u'-').replace(locale.negativeSign(),
"-"_L1);
509 if (
auto match = matchOffsetText(text.sliced(cut), mapped, locale, scale)) {
510 if (text.sliced(cut + match.size).startsWith(gmtSuffix))
511 return { gmtSize + match.size, -match.offset };
515 if (gmtSize > 0 && text.sliced(cut).startsWith(gmtSuffix))
516 return { gmtSize, 0 };
525namespace QtTimeZoneLocale {
529 QList<QByteArrayView> result;
531 const TerritoryZone *row =
532 std::lower_bound(std::begin(territoryZoneMap), std::end(territoryZoneMap),
534 [](
const TerritoryZone &row, qint16 territory) {
535 return row.territory < territory;
537 if (row != std::end(territoryZoneMap) && QLocale::Territory(row->territory) == territory)
538 result << row->ianaId();
540 for (
const MetaZoneData &row : metaZoneTable) {
541 if (QLocale::Territory(row.territory) == territory)
542 result << row.ianaId();
550QString zoneOffsetFormat(
const QLocale &locale, qsizetype locInd, QLocale::FormatType width,
551 const QDateTime &,
int offsetSeconds)
557 const LocaleZoneData &locData = localeZoneData[locInd];
559 auto hourFormatR = offsetSeconds < 0 ? locData.negHourFormat() : locData.posHourFormat();
560 QStringView hourFormat = hourFormatR.viewData(hourFormatTable);
561 Q_ASSERT(!hourFormat.isEmpty());
563 offsetSeconds = qAbs(offsetSeconds);
566 const int offsetMinutes = (offsetSeconds + 29 + (1 & (offsetSeconds / 60))) / 60;
568 const QString hourOffset = formatOffset(hourFormat, offsetMinutes, locale, width);
569 if (width == QLocale::ShortFormat)
572 QStringView offsetFormat = locData.offsetGmtFormat().viewData(gmtFormatTable);
573 Q_ASSERT(!offsetFormat.isEmpty());
574 return offsetFormat.arg(hourOffset);
579QString QTimeZonePrivate::localeName(qint64 atMSecsSinceEpoch,
int offsetFromUtc,
580 QTimeZone::TimeType timeType,
581 QTimeZone::NameType nameType,
582 const QLocale &locale)
const
584 if (nameType == QTimeZone::OffsetName) {
586 return QtTimeZoneLocale::zoneOffsetFormat(locale, locale.d->m_index, QLocale::LongFormat,
587 QDateTime(), offsetFromUtc);
592 QByteArray ianaAbbrev, ianaTail;
593 const auto scanIana = [&](QByteArrayView iana) {
597 if (!ianaAbbrev.isEmpty() && !ianaTail.isEmpty())
599 qsizetype cut = iana.lastIndexOf(
'/');
600 QByteArrayView tail = cut < 0 ? iana : iana.sliced(cut + 1);
602 if (tail ==
"McMurdo") {
603 if (ianaTail.isEmpty())
604 ianaTail =
"McMurdo"_ba;
606 }
else if (tail ==
"DumontDUrville") {
607 if (ianaTail.isEmpty())
608 ianaTail =
"Dumont d'Urville"_ba;
610 }
else if (tail.isEmpty()) {
616 bool maybeAbbr = ianaAbbrev.isEmpty(), maybeCityName = ianaTail.isEmpty(), inword =
false;
618 for (
char ch : tail) {
619 if (ch ==
'+' || ch ==
'-') {
620 if (ch ==
'+' || !inword)
621 maybeCityName =
false;
629 }
else if (ch ==
'_') {
632 maybeCityName =
false;
634 }
else if (QChar::isLower(ch)) {
638 }
else if (QChar::isUpper(ch)) {
642 maybeCityName =
false;
644 }
else if (QChar::isDigit(ch)) {
647 maybeCityName =
false;
651 if (!maybeAbbr && !maybeCityName)
654 if (maybeAbbr && maybeCityName)
658 if (tail.endsWith(
"-0") || tail.endsWith(
"+0"))
659 tail = tail.chopped(2);
660 ianaAbbrev = tail.toByteArray();
661 if (sign && iana.startsWith(
"Etc/")) {
663 ianaAbbrev = ianaAbbrev.replace(
'-',
'+');
664 else if (sign ==
'+')
665 ianaAbbrev = ianaAbbrev.replace(
'+',
'-');
669 ianaTail = tail.toByteArray().replace(
'_',
' ');
673 if (QByteArray iana = aliasToIana(m_id); !iana.isEmpty() && iana != m_id)
677#define tableLookup(table, member, sought, test)
678 findTableEntryFor(QSpan(table).first(nextData.member).sliced(locData.member), sought, test)
682 const QList<qsizetype> indices = fallbackLocalesFor(locale.d->m_index);
683 QString exemplarCity;
684 const auto metaIdBefore = [](
auto &row, quint16 key) {
return row.metaIdIndex < key; };
687 for (
const qsizetype locInd : indices) {
688 const LocaleZoneData &locData = localeZoneData[locInd];
690 Q_ASSERT(std::size_t(locInd) < std::size(localeZoneData) - 1);
691 const LocaleZoneData &nextData = localeZoneData[locInd + 1];
693 QByteArrayView iana{m_id};
694 if (quint16 metaKey = metaZoneAt(iana, atMSecsSinceEpoch)) {
695 if (
const MetaZoneData *metaFrom = metaZoneStart(metaKey)) {
696 quint16 metaIdIndex = metaFrom->metaIdIndex;
697 QLocaleData::DataRange range{0, 0};
698 const char16_t *strings =
nullptr;
699 if (nameType == QTimeZone::ShortName) {
700 auto row =
tableLookup(localeMetaZoneShortNameTable, m_metaShortTableStart,
701 metaIdIndex, metaIdBefore);
702 if (row && row->metaIdIndex == metaIdIndex) {
703 range = row->shortName(timeType);
704 strings = shortMetaZoneNameTable;
707 auto row =
tableLookup(localeMetaZoneLongNameTable, m_metaLongTableStart,
708 metaIdIndex, metaIdBefore);
709 if (row && row->metaIdIndex == metaIdIndex) {
710 range = row->longName(timeType);
711 strings = longMetaZoneNameTable;
714 Q_ASSERT(strings || !range.size);
717 return range.getData(strings);
719 if (
const auto *metaRow = metaZoneDataFor(metaFrom, locale.territory()))
720 iana = metaRow->ianaId();
725 if (exemplarCity.isEmpty()) {
726 exemplarCity = exemplarCityFor(locData, nextData, m_id);
727 if (exemplarCity.isEmpty())
728 exemplarCity = exemplarCityFor(locData, nextData, iana);
737 localeZoneNameTable, m_zoneTableStart,
738 iana, [](
auto &row, QByteArrayView key) {
return row.ianaId() < key; });
739 if (row && row->ianaId() == iana) {
740 QLocaleData::DataRange range = row->name(nameType, timeType);
742 auto table = nameType == QTimeZone::ShortName
745 return range.getData(table);
748 }
while (std::exchange(iana, QByteArrayView{m_id}) != m_id);
753 if (exemplarCity.isEmpty() && !ianaTail.isEmpty())
754 exemplarCity = QString::fromLatin1(ianaTail);
757 case QTimeZone::DefaultName:
758 case QTimeZone::LongName:
759 for (
const qsizetype locInd : indices) {
760 const LocaleZoneData &locData = localeZoneData[locInd];
761 QStringView regionFormat
762 = locData.regionFormatRange(timeType).viewData(regionFormatTable);
763 if (!regionFormat.isEmpty()) {
764 QString where = exemplarCity;
766 if (!where.isEmpty())
767 return regionFormat.arg(where);
771 for (
const qsizetype locInd : indices) {
772 const LocaleZoneData &locData = localeZoneData[locInd];
773 QStringView fallbackFormat = locData.fallbackFormat().viewData(fallbackFormatTable);
781 case QTimeZone::ShortName:
783 if (!ianaAbbrev.isEmpty())
784 return QString::fromLatin1(ianaAbbrev);
787 case QTimeZone::OffsetName:
788 Q_UNREACHABLE_RETURN(QString());
796 return QtTimeZoneLocale::zoneOffsetFormat(locale, locale.d->m_index, QLocale::NarrowFormat,
797 QDateTime(), offsetFromUtc);
802QTimeZonePrivate::NamePrefixMatch
803QTimeZonePrivate::findLongNamePrefix(QStringView text,
const QLocale &locale,
804 std::optional<qint64> atEpochMillis)
806 constexpr std::size_t invalidMetaId = std::size(metaIdData);
807 constexpr std::size_t invalidIanaId = std::size(ianaIdData);
808 constexpr QTimeZone::TimeType timeTypes[] = {
810 QTimeZone::GenericTime,
811 QTimeZone::StandardTime,
812 QTimeZone::DaylightTime,
815 qsizetype nameLength = 0;
816 QTimeZone::TimeType timeType = QTimeZone::GenericTime;
817 quint16 ianaIdIndex = invalidIanaId;
818 quint16 metaIdIndex = invalidMetaId;
819 QLocale::Territory where = QLocale::AnyTerritory;
821#define localeRows(table, member) QSpan(table).first(nextData.member).sliced(locData.member)
823 const QList<qsizetype> indices = fallbackLocalesFor(locale.d->m_index);
824 for (
const qsizetype locInd : indices) {
825 const LocaleZoneData &locData = localeZoneData[locInd];
827 Q_ASSERT(std::size_t(locInd) < std::size(localeZoneData) - 1);
828 const LocaleZoneData &nextData = localeZoneData[locInd + 1];
830 const auto metaRows =
localeRows(localeMetaZoneLongNameTable, m_metaLongTableStart);
831 for (
const LocaleMetaZoneLongNames &row : metaRows) {
832 for (
const QTimeZone::TimeType type : timeTypes) {
833 QLocaleData::DataRange range = row.longName(type);
834 if (range.size > best.nameLength) {
835 QStringView name = range.viewData(longMetaZoneNameTable);
836 if (text.startsWith(name)) {
837 best = {
static_cast<qsizetype>(range.size), type,
838 invalidIanaId, row.metaIdIndex };
839 if (best.nameLength >= text.size())
844 if (best.nameLength >= text.size())
848 const auto ianaRows =
localeRows(localeZoneNameTable, m_zoneTableStart);
849 for (
const LocaleZoneNames &row : ianaRows) {
850 for (
const QTimeZone::TimeType type : timeTypes) {
851 QLocaleData::DataRange range = row.longName(type);
852 if (range.size > best.nameLength) {
853 QStringView name = range.viewData(longZoneNameTable);
855 bool gotZone = row.ianaIdIndex == best.ianaIdIndex
856 || QTimeZone::isTimeZoneIdAvailable(row.ianaId().toByteArray());
857 if (text.startsWith(name) && gotZone)
858 best = {
static_cast<qsizetype>(range.size), type, row.ianaIdIndex };
864 if (best.metaIdIndex != invalidMetaId) {
865 const auto metaIdBefore = [](
auto &row, quint16 key) {
return row.metaIdIndex < key; };
868 const MetaZoneData *metaRow =
869 std::lower_bound(std::begin(metaZoneTable), std::end(metaZoneTable),
870 best.metaIdIndex, metaIdBefore);
872 for (; metaRow < std::end(metaZoneTable)
873 && metaRow->metaIdIndex == best.metaIdIndex; ++metaRow) {
874 auto metaLand = QLocale::Territory(metaRow->territory);
877 if ((best.where == QLocale::AnyTerritory || metaLand == QLocale::World)
879 ? metaRow->metaZoneKey == metaZoneAt(metaRow->ianaId(), *atEpochMillis)
880 : zoneEverInMeta(metaRow->ianaId(), metaRow->metaZoneKey))) {
881 if (metaRow->ianaIdIndex == best.ianaIdIndex
882 || QTimeZone::isTimeZoneIdAvailable(metaRow->ianaId().toByteArray())) {
883 best.ianaIdIndex = metaRow->ianaIdIndex;
884 best.where = metaLand;
885 if (best.where == QLocale::World)
891 if (best.ianaIdIndex != invalidIanaId)
892 return { QByteArray(ianaIdData + best.ianaIdIndex), best.nameLength, best.timeType };
896 for (
const qsizetype locInd : indices) {
897 const LocaleZoneData &locData = localeZoneData[locInd];
898 const LocaleZoneData &nextData = localeZoneData[locInd + 1];
899 for (
const QTimeZone::TimeType timeType : timeTypes) {
900 QStringView regionFormat
901 = locData.regionFormatRange(timeType).viewData(regionFormatTable);
903 const qsizetype cut = regionFormat.indexOf(u"%0");
907 QStringView prefix = regionFormat.first(cut);
909 if (cut > 0 && !text.startsWith(prefix))
911 QStringView suffix = regionFormat.sliced(cut + 2);
913 QStringView tail = text.sliced(cut);
916 if (suffix.size() && tail.indexOf(suffix) < 0)
921 const auto textMatches = [tail, suffix](QStringView where) {
922 return (where.isEmpty() || tail.startsWith(where))
923 && (suffix.isEmpty() || tail.sliced(where.size()).startsWith(suffix));
926 const auto cityRows =
localeRows(localeZoneExemplarTable, m_exemplarTableStart);
927 for (
const LocaleZoneExemplar &row : cityRows) {
928 QStringView city = row.exemplarCity().viewData(exemplarCityTable);
929 if (textMatches(city)) {
930 qsizetype length = cut + city.size() + suffix.size();
931 if (length > best.nameLength) {
932 bool gotZone = row.ianaIdIndex == best.ianaIdIndex
933 || QTimeZone::isTimeZoneIdAvailable(row.ianaId().toByteArray());
935 best = { length, timeType, row.ianaIdIndex };
940 const QList<QByteArray> allZones = QTimeZone::availableTimeZoneIds();
941 for (
const auto &iana : allZones) {
942 Q_ASSERT(!iana.isEmpty());
943 qsizetype slash = iana.lastIndexOf(
'/');
944 QByteArray local = slash > 0 ? iana.sliced(slash + 1) : iana;
945 QString city = QString::fromLatin1(local.replace(
'_',
' '));
946 if (textMatches(city)) {
947 qsizetype length = cut + city.size() + suffix.size();
948 if (length > best.nameLength) {
952 QByteArrayView run(ianaIdData, qstrlen(ianaIdData));
954 const char *stop = ianaIdData + std::size(ianaIdData) - 1;
955 while (run != iana) {
956 if (run.end() < stop) {
957 run = QByteArrayView(run.end() + 1);
959 run = QByteArrayView();
963 if (!run.isEmpty()) {
964 Q_ASSERT(run == iana);
965 const auto ianaIdIndex = run.begin() - ianaIdData;
966 Q_ASSERT(ianaIdIndex <= (std::numeric_limits<quint16>::max)());
967 best = { length, timeType, quint16(ianaIdIndex) };
975 if (best.ianaIdIndex != invalidIanaId)
976 return { QByteArray(ianaIdData + best.ianaIdIndex), best.nameLength, best.timeType };
982QTimeZonePrivate::NamePrefixMatch
983QTimeZonePrivate::findNarrowOffsetPrefix(QStringView text,
const QLocale &locale,
984 QLocale::FormatType scale)
986 if (
auto match = matchOffsetFormat(text, locale, locale.d->m_index, scale)) {
988 if (QTimeZone::MinUtcOffsetSecs <= match.offset
989 && match.offset <= QTimeZone::MaxUtcOffsetSecs) {
994 return { isoOffsetFormat(match.offset, QTimeZone::OffsetName).toLatin1(),
995 match.size, QTimeZone::GenericTime };
QList< QByteArrayView > ianaIdsForTerritory(QLocale::Territory territory)
#define tableLookup(table, member, sought, test)
#define localeRows(table, member)