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()
const {
return size > 0; }
389OffsetFormatMatch matchOffsetText(QStringView text, QStringView format,
const QLocale &locale,
390 QLocale::FormatType scale)
395 OffsetFormatMatch res;
401 const QString zero = locale.zeroDigit();
402 qsizetype cut = format.indexOf(u'H');
403 if (cut < 0 || !text.startsWith(format.first(cut)) || !format.endsWith(u"mm"))
405 text = text.sliced(cut);
406 QStringView sep = format.sliced(cut).chopped(2);
408 while (hlen < sep.size() && sep[hlen] == u'H')
410 sep = sep.sliced(hlen);
412 const auto hasDigitAt = [digitWidth = zero.size(), text](qsizetype index) {
414 return index < text.size() && text[index].isDigit();
415 Q_ASSERT(digitWidth == 2);
416 const qsizetype offset = index * 2;
417 if (offset + 1 >= text.size())
419 if (!text[offset].isHighSurrogate() || !text[offset + 1].isLowSurrogate())
421 const char32_t ch = QChar::surrogateToUcs4(text[offset], text[offset + 1]);
422 return QChar::isDigit(ch);
425 while (digits < 4 && hasDigitAt(digits))
433 if (scale == QLocale::NarrowFormat || (hlen < 2 && !text.startsWith(zero)))
435 else if (digits < hlen + 2)
437 minStr = text.sliced(hlen * zero.size()).first(2 * zero.size());
438 }
else if (scale == QLocale::NarrowFormat) {
440 }
else if (hlen != digits) {
444 const qsizetype sepAt = text.indexOf(sep);
445 if (digits * zero.size() < sepAt)
447 if (scale == QLocale::NarrowFormat || (hlen < 2 && !text.startsWith(zero)))
449 else if (digits != hlen)
451 if (sepAt >= 0 && text.size() >= sepAt + sep.size() + 2 * zero.size())
452 minStr = text.sliced(sepAt + sep.size()).first(2 * zero.size());
453 else if (scale != QLocale::NarrowFormat)
456 minStr = text.sliced(sepAt + sep.size());
462 uint minute = minStr.isEmpty() ? 0 : locale.toUInt(minStr, &ok);
463 if (!ok && scale == QLocale::NarrowFormat) {
468 if (ok && minute < 60) {
469 uint hour = locale.toUInt(text.first(hlen * zero.size()), &ok);
471 res.offset = (hour * 60 + minute) * 60;
472 res.size = cut + hlen * zero.size();
473 if (!minStr.isEmpty())
474 res.size += sep.size() + minStr.size();
480OffsetFormatMatch matchOffsetFormat(QStringView text,
const QLocale &locale, qsizetype locInd,
481 QLocale::FormatType scale)
483 const LocaleZoneData &locData = localeZoneData[locInd];
484 const QStringView posHourForm = locData.posHourFormat().viewData(hourFormatTable);
485 const QStringView negHourForm = locData.negHourFormat().viewData(hourFormatTable);
487 const bool mapNeg = text.contains(u'-')
488 && (negHourForm.contains(u'\u2212') || negHourForm.contains(locale.negativeSign()));
490 if (scale == QLocale::ShortFormat) {
491 if (
auto match = matchOffsetText(text, posHourForm, locale, scale))
493 if (
auto match = matchOffsetText(text, negHourForm, locale, scale))
494 return { match.size, -match.offset };
496 const QString mapped = negHourForm.toString()
497 .replace(u'\u2212', u'-').replace(locale.negativeSign(),
"-"_L1);
498 if (
auto match = matchOffsetText(text, mapped, locale, scale))
499 return { match.size, -match.offset };
502 const QStringView offsetFormat = locData.offsetGmtFormat().viewData(gmtFormatTable);
503 if (
const qsizetype cut = offsetFormat.indexOf(u"%0"); cut >= 0) {
504 const QStringView gmtPrefix = offsetFormat.first(cut);
505 const QStringView gmtSuffix = offsetFormat.sliced(cut + 2);
506 const qsizetype gmtSize = cut + gmtSuffix.size();
507 const auto crossMatch = [gmtPrefix, text]
508 (QLatin1StringView lhs, QLatin1StringView rhs) {
509 const qsizetype len = lhs.size();
510 Q_ASSERT(len == rhs.size());
511 if (!gmtPrefix.startsWith(lhs) || !text.startsWith(rhs))
513 if (gmtPrefix.size() == len)
515 return text.sliced(len).startsWith(gmtPrefix.sliced(len));
519 if ((gmtPrefix.isEmpty() || text.startsWith(gmtPrefix)
522 || crossMatch(
"GMT"_L1,
"UTC"_L1) || crossMatch(
"UTC"_L1,
"GMT"_L1))
523 && (gmtSuffix.isEmpty() || text.sliced(cut).indexOf(gmtSuffix) >= 0)) {
524 if (
auto match = matchOffsetText(text.sliced(cut), posHourForm, locale, scale)) {
525 if (text.sliced(cut + match.size).startsWith(gmtSuffix))
526 return { gmtSize + match.size, match.offset };
528 if (
auto match = matchOffsetText(text.sliced(cut), negHourForm, locale, scale)) {
529 if (text.sliced(cut + match.size).startsWith(gmtSuffix))
530 return { gmtSize + match.size, -match.offset };
532 const QString mapped = negHourForm.toString()
533 .replace(u'\u2212', u'-').replace(locale.negativeSign(),
"-"_L1);
534 if (
auto match = matchOffsetText(text.sliced(cut), mapped, locale, scale)) {
535 if (text.sliced(cut + match.size).startsWith(gmtSuffix))
536 return { gmtSize + match.size, -match.offset };
540 if (gmtSize > 0 && text.sliced(cut).startsWith(gmtSuffix))
541 return { gmtSize, 0 };
550namespace QtTimeZoneLocale {
554 QList<QByteArrayView> result;
556 const TerritoryZone *row =
557 std::lower_bound(std::begin(territoryZoneMap), std::end(territoryZoneMap),
559 [](
const TerritoryZone &row, qint16 territory) {
560 return row.territory < territory;
562 if (row != std::end(territoryZoneMap) && QLocale::Territory(row->territory) == territory)
563 result << row->ianaId();
565 for (
const MetaZoneData &row : metaZoneTable) {
566 if (QLocale::Territory(row.territory) == territory)
567 result << row.ianaId();
575QString zoneOffsetFormat(
const QLocale &locale, qsizetype locInd, QLocale::FormatType width,
576 const QDateTime &,
int offsetSeconds)
585 const LocaleZoneData &locData = localeZoneData[locInd];
587 auto hourFormatR = offsetSeconds < 0 ? locData.negHourFormat() : locData.posHourFormat();
588 QStringView hourFormat = hourFormatR.viewData(hourFormatTable);
589 Q_ASSERT(!hourFormat.isEmpty());
591 offsetSeconds = qAbs(offsetSeconds);
594 const int offsetMinutes = (offsetSeconds + 29 + (1 & (offsetSeconds / 60))) / 60;
596 const QString hourOffset = formatOffset(hourFormat, offsetMinutes, locale, width);
597 if (width == QLocale::ShortFormat)
600 QStringView offsetFormat = locData.offsetGmtFormat().viewData(gmtFormatTable);
601 Q_ASSERT(!offsetFormat.isEmpty());
602 return offsetFormat.arg(hourOffset);
607QString QTimeZonePrivate::localeName(qint64 atMSecsSinceEpoch,
int offsetFromUtc,
608 QTimeZone::TimeType timeType,
609 QTimeZone::NameType nameType,
610 const QLocale &locale)
const
612 if (nameType == QTimeZone::OffsetName) {
614 return QtTimeZoneLocale::zoneOffsetFormat(locale, locale.d->m_index, QLocale::LongFormat,
615 QDateTime(), offsetFromUtc);
620 QByteArray ianaAbbrev, ianaTail;
621 const auto scanIana = [&](QByteArrayView iana) {
625 if (!ianaAbbrev.isEmpty() && !ianaTail.isEmpty())
627 qsizetype cut = iana.lastIndexOf(
'/');
628 QByteArrayView tail = cut < 0 ? iana : iana.sliced(cut + 1);
630 if (tail ==
"McMurdo") {
631 if (ianaTail.isEmpty())
632 ianaTail =
"McMurdo"_ba;
634 }
else if (tail ==
"DumontDUrville") {
635 if (ianaTail.isEmpty())
636 ianaTail =
"Dumont d'Urville"_ba;
638 }
else if (tail.isEmpty()) {
642 const auto isMixedCaseAbbrev = [tail](
char ch) {
646 switch (tail.size()) {
647 case 2:
return tail ==
"Gk";
648 case 3:
return tail ==
"CuT";
650 if (tail[0] ==
'C' && tail[1] == ch && tail[3] ==
'T') {
652 case 'h':
return tail[2] ==
'S';
653 case 'u':
return tail[2] ==
'S' || tail[2] ==
'D';
665 bool maybeAbbr = ianaAbbrev.isEmpty(), maybeCityName = ianaTail.isEmpty(), inword =
false;
667 for (
char ch : tail) {
668 if (ch ==
'+' || ch ==
'-') {
669 if (ch ==
'+' || !inword)
670 maybeCityName =
false;
678 }
else if (ch ==
'_') {
681 maybeCityName =
false;
683 }
else if (QChar::isLower(ch)) {
684 maybeAbbr = isMixedCaseAbbrev(ch);
687 }
else if (QChar::isUpper(ch)) {
691 maybeCityName =
false;
693 }
else if (QChar::isDigit(ch)) {
696 maybeCityName =
false;
700 if (!maybeAbbr && !maybeCityName)
703 if (maybeAbbr && maybeCityName)
707 if (tail.endsWith(
"-0") || tail.endsWith(
"+0"))
708 tail = tail.chopped(2);
709 ianaAbbrev = tail.toByteArray();
710 if (sign && iana.startsWith(
"Etc/")) {
712 ianaAbbrev = ianaAbbrev.replace(
'-',
'+');
713 else if (sign ==
'+')
714 ianaAbbrev = ianaAbbrev.replace(
'+',
'-');
720 ianaTail = tail.toByteArray().replace(
'_',
' ');
724 if (QByteArray iana = aliasToIana(m_id); !iana.isEmpty() && iana != m_id)
728#define tableLookup(table, member, sought, test)
729 findTableEntryFor(QSpan(table).first(nextData.member).sliced(locData.member), sought, test)
733 const QList<qsizetype> indices = fallbackLocalesFor(locale.d->m_index);
734 QString exemplarCity;
735 const auto metaIdBefore = [](
auto &row, quint16 key) {
return row.metaIdIndex < key; };
738 for (
const qsizetype locInd : indices) {
739 const LocaleZoneData &locData = localeZoneData[locInd];
741 Q_ASSERT(std::size_t(locInd) < std::size(localeZoneData) - 1);
742 const LocaleZoneData &nextData = localeZoneData[locInd + 1];
744 QByteArrayView iana{m_id};
745 if (quint16 metaKey = metaZoneAt(iana, atMSecsSinceEpoch)) {
746 if (
const MetaZoneData *metaFrom = metaZoneStart(metaKey)) {
747 quint16 metaIdIndex = metaFrom->metaIdIndex;
748 QLocaleData::DataRange range{0, 0};
749 const char16_t *strings =
nullptr;
750 if (nameType == QTimeZone::ShortName) {
751 auto row =
tableLookup(localeMetaZoneShortNameTable, m_metaShortTableStart,
752 metaIdIndex, metaIdBefore);
753 if (row && row->metaIdIndex == metaIdIndex) {
754 range = row->shortName(timeType);
755 strings = shortMetaZoneNameTable;
758 auto row =
tableLookup(localeMetaZoneLongNameTable, m_metaLongTableStart,
759 metaIdIndex, metaIdBefore);
760 if (row && row->metaIdIndex == metaIdIndex) {
761 range = row->longName(timeType);
762 strings = longMetaZoneNameTable;
765 Q_ASSERT(strings || !range.size);
768 return range.getData(strings);
770 if (
const auto *metaRow = metaZoneDataFor(metaFrom, locale.territory()))
771 iana = metaRow->ianaId();
776 if (exemplarCity.isEmpty()) {
777 exemplarCity = exemplarCityFor(locData, nextData, m_id);
778 if (exemplarCity.isEmpty())
779 exemplarCity = exemplarCityFor(locData, nextData, iana);
788 localeZoneNameTable, m_zoneTableStart,
789 iana, [](
auto &row, QByteArrayView key) {
return row.ianaId() < key; });
790 if (row && row->ianaId() == iana) {
791 QLocaleData::DataRange range = row->name(nameType, timeType);
793 auto table = nameType == QTimeZone::ShortName
796 return range.getData(table);
799 }
while (std::exchange(iana, QByteArrayView{m_id}) != m_id);
804 if (exemplarCity.isEmpty() && !ianaTail.isEmpty())
805 exemplarCity = QString::fromLatin1(ianaTail);
808 case QTimeZone::DefaultName:
809 case QTimeZone::LongName:
810 for (
const qsizetype locInd : indices) {
811 const LocaleZoneData &locData = localeZoneData[locInd];
812 QStringView regionFormat
813 = locData.regionFormatRange(timeType).viewData(regionFormatTable);
814 if (!regionFormat.isEmpty()) {
815 QString where = exemplarCity;
817 if (!where.isEmpty())
818 return regionFormat.arg(where);
822 for (
const qsizetype locInd : indices) {
823 const LocaleZoneData &locData = localeZoneData[locInd];
824 QStringView fallbackFormat = locData.fallbackFormat().viewData(fallbackFormatTable);
832 case QTimeZone::ShortName:
834 if (!ianaAbbrev.isEmpty())
835 return QString::fromLatin1(ianaAbbrev);
838 case QTimeZone::OffsetName:
839 Q_UNREACHABLE_RETURN(QString());
847 return QtTimeZoneLocale::zoneOffsetFormat(locale, locale.d->m_index, QLocale::NarrowFormat,
848 QDateTime(), offsetFromUtc);
853QTimeZonePrivate::NamePrefixMatch
854QTimeZonePrivate::findLongNamePrefix(QStringView text,
const QLocale &locale,
855 std::optional<qint64> atEpochMillis)
857 constexpr std::size_t invalidMetaId = std::size(metaIdData);
858 constexpr std::size_t invalidIanaId = std::size(ianaIdData);
859 constexpr QTimeZone::TimeType timeTypes[] = {
861 QTimeZone::GenericTime,
862 QTimeZone::StandardTime,
863 QTimeZone::DaylightTime,
866 qsizetype nameLength = 0;
867 QTimeZone::TimeType timeType = QTimeZone::GenericTime;
868 quint16 ianaIdIndex = invalidIanaId;
869 quint16 metaIdIndex = invalidMetaId;
870 QLocale::Territory where = QLocale::AnyTerritory;
872#define localeRows(table, member) QSpan(table).first(nextData.member).sliced(locData.member)
874 const QList<qsizetype> indices = fallbackLocalesFor(locale.d->m_index);
875 for (
const qsizetype locInd : indices) {
876 const LocaleZoneData &locData = localeZoneData[locInd];
878 Q_ASSERT(std::size_t(locInd) < std::size(localeZoneData) - 1);
879 const LocaleZoneData &nextData = localeZoneData[locInd + 1];
881 const auto metaRows =
localeRows(localeMetaZoneLongNameTable, m_metaLongTableStart);
882 for (
const LocaleMetaZoneLongNames &row : metaRows) {
883 for (
const QTimeZone::TimeType type : timeTypes) {
884 QLocaleData::DataRange range = row.longName(type);
885 if (range.size > best.nameLength) {
886 QStringView name = range.viewData(longMetaZoneNameTable);
887 if (text.startsWith(name)) {
888 best = {
static_cast<qsizetype>(range.size), type,
889 invalidIanaId, row.metaIdIndex };
890 if (best.nameLength >= text.size())
895 if (best.nameLength >= text.size())
899 const auto ianaRows =
localeRows(localeZoneNameTable, m_zoneTableStart);
900 for (
const LocaleZoneNames &row : ianaRows) {
901 for (
const QTimeZone::TimeType type : timeTypes) {
902 QLocaleData::DataRange range = row.longName(type);
903 if (range.size > best.nameLength) {
904 QStringView name = range.viewData(longZoneNameTable);
906 bool gotZone = row.ianaIdIndex == best.ianaIdIndex
907 || QTimeZone::isTimeZoneIdAvailable(row.ianaId().toByteArray());
908 if (text.startsWith(name) && gotZone)
909 best = {
static_cast<qsizetype>(range.size), type, row.ianaIdIndex };
915 if (best.metaIdIndex != invalidMetaId) {
916 const auto metaIdBefore = [](
auto &row, quint16 key) {
return row.metaIdIndex < key; };
919 const MetaZoneData *metaRow =
920 std::lower_bound(std::begin(metaZoneTable), std::end(metaZoneTable),
921 best.metaIdIndex, metaIdBefore);
923 for (; metaRow < std::end(metaZoneTable)
924 && metaRow->metaIdIndex == best.metaIdIndex; ++metaRow) {
925 auto metaLand = QLocale::Territory(metaRow->territory);
928 if ((best.where == QLocale::AnyTerritory || metaLand == QLocale::World)
930 ? metaRow->metaZoneKey == metaZoneAt(metaRow->ianaId(), *atEpochMillis)
931 : zoneEverInMeta(metaRow->ianaId(), metaRow->metaZoneKey))) {
932 if (metaRow->ianaIdIndex == best.ianaIdIndex
933 || QTimeZone::isTimeZoneIdAvailable(metaRow->ianaId().toByteArray())) {
934 best.ianaIdIndex = metaRow->ianaIdIndex;
935 best.where = metaLand;
936 if (best.where == QLocale::World)
942 if (best.ianaIdIndex != invalidIanaId)
943 return { QByteArray(ianaIdData + best.ianaIdIndex), best.nameLength, best.timeType };
949 NamePrefixMatch found;
950 for (
const qsizetype locInd : indices) {
951 const LocaleZoneData &locData = localeZoneData[locInd];
952 const LocaleZoneData &nextData = localeZoneData[locInd + 1];
953 for (
const QTimeZone::TimeType timeType : timeTypes) {
954 QStringView regionFormat
955 = locData.regionFormatRange(timeType).viewData(regionFormatTable);
957 const qsizetype cut = regionFormat.indexOf(u"%0");
961 QStringView prefix = regionFormat.first(cut);
963 if (cut > 0 && !text.startsWith(prefix))
965 QStringView suffix = regionFormat.sliced(cut + 2);
967 QStringView tail = text.sliced(cut);
970 if (suffix.size() && tail.indexOf(suffix) < 0)
975 const auto textMatches = [tail, suffix](QStringView where) {
976 return (where.isEmpty() || tail.startsWith(where))
977 && (suffix.isEmpty() || tail.sliced(where.size()).startsWith(suffix));
980 const auto cityRows =
localeRows(localeZoneExemplarTable, m_exemplarTableStart);
981 for (
const LocaleZoneExemplar &row : cityRows) {
982 QStringView city = row.exemplarCity().viewData(exemplarCityTable);
983 if (textMatches(city)) {
984 qsizetype length = cut + city.size() + suffix.size();
985 if (length > found.nameLength) {
986 bool gotZone = row.ianaId() == found.ianaId
987 || QTimeZone::isTimeZoneIdAvailable(row.ianaId().toByteArray());
989 found = { row.ianaId().toByteArray(), length, timeType };
994 const QList<QByteArray> allZones = QTimeZone::availableTimeZoneIds();
995 for (
const auto &iana : allZones) {
996 Q_ASSERT(!iana.isEmpty());
997 qsizetype slash = iana.lastIndexOf(
'/');
998 QByteArray local = slash > 0 ? iana.sliced(slash + 1) : iana;
999 QString city = QString::fromLatin1(local.replace(
'_',
' '));
1000 if (textMatches(city)) {
1001 qsizetype length = cut + city.size() + suffix.size();
1002 if (length > found.nameLength)
1003 found = { iana, length, timeType };
1014QTimeZonePrivate::NamePrefixMatch
1015QTimeZonePrivate::findNarrowOffsetPrefix(QStringView text,
const QLocale &locale)
1018 if (
const auto match = matchOffsetFormat(text, locale, locale.d->m_index,
1019 QLocale::NarrowFormat)) {
1021 if (QTimeZone::MinUtcOffsetSecs <= match.offset
1022 && match.offset <= QTimeZone::MaxUtcOffsetSecs) {
1027 return { isoOffsetFormat(match.offset, QTimeZone::OffsetName).toLatin1(),
1028 match.size, QTimeZone::GenericTime };
QList< QByteArrayView > ianaIdsForTerritory(QLocale::Territory territory)
#define tableLookup(table, member, sought, test)
#define localeRows(table, member)