4#include <private/qtimezonelocale_p.h>
5#include <private/qtimezoneprivate_p.h>
8# include <QtCore/qspan.h>
9# include <private/qdatetime_p.h>
11# include "qtimezonelocale_data_p.h"
12# include "qtimezoneprivate_data_p.h"
13# ifdef QT_CLDR_ZONE_DEBUG
14# include "../text/qlocale_data_p.h"
16static_assert(std::size(locale_data) == std::size(QtTimeZoneLocale::localeZoneData));
19 for (std::size_t i = 0; i < std::size(locale_data); ++i) {
20 const auto &loc = locale_data[i];
21 const auto &zone = QtTimeZoneLocale::localeZoneData[i];
22 if (loc.m_language_id != zone.m_language_id
23 || loc.m_script_id != zone.m_script_id
24 || loc.m_territory_id != zone.m_territory_id) {
36using namespace Qt::StringLiterals;
42UCalendarDisplayNameType ucalDisplayNameType(QTimeZone::TimeType timeType,
43 QTimeZone::NameType nameType)
48 case QTimeZone::ShortName:
49 return timeType == QTimeZone::DaylightTime ? UCAL_SHORT_DST : UCAL_SHORT_STANDARD;
50 case QTimeZone::DefaultName:
51 case QTimeZone::LongName:
52 return timeType == QTimeZone::DaylightTime ? UCAL_DST : UCAL_STANDARD;
53 case QTimeZone::OffsetName:
56 Q_UNREACHABLE_RETURN(UCAL_STANDARD);
61namespace QtTimeZoneLocale {
65QString ucalTimeZoneDisplayName(UCalendar *ucal,
66 QTimeZone::TimeType timeType,
67 QTimeZone::NameType nameType,
68 const QByteArray &localeCode)
70 constexpr int32_t BigNameLength = 50;
71 int32_t size = BigNameLength;
72 QString result(size, Qt::Uninitialized);
73 auto dst = [&result]() {
return reinterpret_cast<UChar *>(result.data()); };
74 UErrorCode status = U_ZERO_ERROR;
75 const UCalendarDisplayNameType utype = ucalDisplayNameType(timeType, nameType);
78 size = ucal_getTimeZoneDisplayName(ucal, utype, localeCode.constData(),
79 dst(), size, &status);
82 if (size > BigNameLength || status == U_BUFFER_OVERFLOW_ERROR) {
84 status = U_ZERO_ERROR;
85 size = ucal_getTimeZoneDisplayName(ucal, utype, localeCode.constData(),
86 dst(), size, &status);
89 if (!U_SUCCESS(status))
100QString QTimeZonePrivate::localeName(qint64 atMSecsSinceEpoch,
int offsetFromUtc,
101 QTimeZone::TimeType timeType,
102 QTimeZone::NameType nameType,
103 const QLocale &locale)
const
105 Q_UNUSED(atMSecsSinceEpoch);
109 if (nameType == QTimeZone::OffsetName)
110 return isoOffsetFormat(offsetFromUtc);
112 const QString id = QString::fromUtf8(m_id);
113 const QByteArray loc = locale.name().toUtf8();
114 UErrorCode status = U_ZERO_ERROR;
116 UCalendar *ucal = ucal_open(
reinterpret_cast<
const UChar *>(id.data()), id.size(),
117 loc.constData(), UCAL_DEFAULT, &status);
118 if (ucal && U_SUCCESS(status)) {
119 auto tidier = qScopeGuard([ucal]() { ucal_close(ucal); });
120 return QtTimeZoneLocale::ucalTimeZoneDisplayName(ucal, timeType, nameType, loc);
125namespace QtTimeZoneLocale {
127using namespace QtTimeZoneCldr;
128constexpr QByteArrayView LocaleZoneExemplar::ianaId()
const {
return ianaIdData + ianaIdIndex; }
129constexpr QByteArrayView LocaleZoneNames::ianaId()
const {
return ianaIdData + ianaIdIndex; }
133using namespace QtTimeZoneLocale;
134using namespace QtTimeZoneCldr;
137template <
typename Row,
typename Sought,
typename Condition>
138const Row *findTableEntryFor(
const QSpan<Row> data, Sought value, Condition test)
143 auto begin = data.begin(), end = data.end();
144 Q_ASSERT(begin == end || end->localeIndex > begin->localeIndex);
145 Q_ASSERT(begin == end || end[-1].localeIndex == begin->localeIndex);
146 auto row = std::lower_bound(begin, end, value, test);
147 return row == end ?
nullptr : row;
150QString exemplarCityFor(
const LocaleZoneData &locale,
const LocaleZoneData &next,
153 auto xct = findTableEntryFor(
154 QSpan(localeZoneExemplarTable).first(next.m_exemplarTableStart
155 ).sliced(locale.m_exemplarTableStart),
156 iana, [](
auto &row, QByteArrayView key) {
return row.ianaId() < key; });
157 if (xct && xct->ianaId() == iana)
158 return xct->exemplarCity().getData(exemplarCityTable);
163quint32 clipEpochMinute(qint64 epochMinute)
167 constexpr quint32 epoch = 0;
176 constexpr quint32 ragnarok = 1 ^ ~epoch;
177 return epochMinute + 1 >= ragnarok ? ragnarok : quint32(epochMinute);
180constexpr bool intervalEndsBefore(
const ZoneMetaHistory &record, quint32 dt)
noexcept
183 return record.end <= dt;
187
188
189
190quint16 metaZoneAt(QByteArrayView zoneId, qint64 atMSecsSinceEpoch)
192 using namespace QtPrivate::DateTimeConstants;
193 auto it = std::lower_bound(std::begin(zoneHistoryTable), std::end(zoneHistoryTable), zoneId,
194 [](
const ZoneMetaHistory &record, QByteArrayView id) {
195 return record.ianaId().compare(id, Qt::CaseInsensitive) < 0;
197 if (it == std::end(zoneHistoryTable) || it->ianaId().compare(zoneId, Qt::CaseInsensitive) > 0)
200 std::upper_bound(it, std::end(zoneHistoryTable), zoneId,
201 [](QByteArrayView id,
const ZoneMetaHistory &record) {
202 return id.compare(record.ianaId(), Qt::CaseInsensitive) < 0;
204 const quint32 dt = clipEpochMinute(atMSecsSinceEpoch / MSECS_PER_MIN);
205 it = std::lower_bound(it, stop, dt, intervalEndsBefore);
206 return it != stop && it->begin <= dt ? it->metaZoneKey : 0;
209constexpr bool dataBeforeMeta(
const MetaZoneData &row, quint16 metaKey)
noexcept
211 return row.metaZoneKey < metaKey;
214constexpr bool metaDataBeforeTerritory(
const MetaZoneData &row, qint16 territory)
noexcept
216 return row.territory < territory;
219const MetaZoneData *metaZoneStart(quint16 metaKey)
221 const MetaZoneData *
const from =
222 std::lower_bound(std::begin(metaZoneTable), std::end(metaZoneTable),
223 metaKey, dataBeforeMeta);
224 if (from == std::end(metaZoneTable) || from->metaZoneKey != metaKey) {
225 qWarning(
"No metazone data found for metazone key %d", metaKey);
231const MetaZoneData *metaZoneDataFor(
const MetaZoneData *from, QLocale::Territory territory)
233 const quint16 metaKey = from->metaZoneKey;
234 const MetaZoneData *
const end =
235 std::lower_bound(from, std::end(metaZoneTable), metaKey + 1, dataBeforeMeta);
236 Q_ASSERT(end != from && end[-1].metaZoneKey == metaKey);
237 QLocale::Territory land = territory;
239 const MetaZoneData *row =
240 std::lower_bound(from, end, qint16(land), metaDataBeforeTerritory);
241 if (row != end && QLocale::Territory(row->territory) == land) {
242 Q_ASSERT(row->metaZoneKey == metaKey);
246 }
while (std::exchange(land, QLocale::World) != QLocale::World);
248 qWarning(
"Metazone %s lacks World data for %ls",
249 from->metaZoneId().constData(),
250 qUtf16Printable(QLocale::territoryToString(territory)));
254QString addPadded(qsizetype width,
const QString &zero,
const QString &number, QString &&onto)
257 width -= number.size() / zero.size();
262 return std::move(onto) + number;
265QString formatOffset(QStringView format,
int offsetMinutes,
const QLocale &locale,
266 QLocale::FormatType form)
268 Q_ASSERT(offsetMinutes >= 0);
269 const QString hour = locale.toString(offsetMinutes / 60);
270 const QString mins = locale.toString(offsetMinutes % 60);
273 const QString zero = locale.zeroDigit();
274 QStringView tail = format;
276 while (!tail.isEmpty()) {
277 if (tail.startsWith(u'\'')) {
278 qsizetype end = tail.indexOf(u'\'', 1);
280 qWarning(
"Unbalanced quote in offset format string: %s",
281 format.toUtf8().constData());
282 return result + tail;
283 }
else if (end == 1) {
286 tail = tail.sliced(2);
289 while (end + 1 < tail.size() && tail[end + 1] == u'\'') {
292 result += tail.sliced(1, end);
293 tail = tail.sliced(end + 1);
294 end = tail.indexOf(u'\'', 1);
296 qWarning(
"Unbalanced quoted quote in offset format string: %s",
297 format.toUtf8().constData());
298 return result + tail;
303 result += tail.sliced(1, end - 1);
304 tail = tail.sliced(end + 1);
306 }
else if (tail.startsWith(u'H')) {
308 while (width < tail.size() && tail[width] == u'H')
310 tail = tail.sliced(width);
311 if (form != QLocale::NarrowFormat)
312 result = addPadded(width, zero, hour, std::move(result));
315 }
else if (tail.startsWith(u'm')) {
317 while (width < tail.size() && tail[width] == u'm')
321 tail = tail.sliced(width);
322 if (form != QLocale::NarrowFormat)
323 result = addPadded(width, zero, mins, std::move(result));
324 else if (offsetMinutes % 60)
326 else if (result.endsWith(u':') || result.endsWith(u'.'))
329 }
else if (tail[0].isHighSurrogate() && tail.size() > 1
330 && tail[1].isLowSurrogate()) {
331 result += tail.first(2);
332 tail = tail.sliced(2);
334 result += tail.front();
335 tail = tail.sliced(1);
343namespace QtTimeZoneLocale {
347 QList<QByteArrayView> result;
349 const TerritoryZone *row =
350 std::lower_bound(std::begin(territoryZoneMap), std::end(territoryZoneMap),
352 [](
const TerritoryZone &row, qint16 territory) {
353 return row.territory < territory;
355 if (row != std::end(territoryZoneMap) && QLocale::Territory(row->territory) == territory)
356 result << row->ianaId();
358 for (
const MetaZoneData &row : metaZoneTable) {
359 if (QLocale::Territory(row.territory) == territory)
360 result << row.ianaId();
368QString zoneOffsetFormat(
const QLocale &locale, qsizetype locInd, QLocale::FormatType width,
369 const QDateTime &,
int offsetSeconds)
375 const LocaleZoneData &locData = localeZoneData[locInd];
377 auto hourFormatR = offsetSeconds < 0 ? locData.negHourFormat() : locData.posHourFormat();
378 QStringView hourFormat = hourFormatR.viewData(hourFormatTable);
379 Q_ASSERT(!hourFormat.isEmpty());
381 offsetSeconds = qAbs(offsetSeconds);
384 const int offsetMinutes = (offsetSeconds + 29 + (1 & (offsetSeconds / 60))) / 60;
386 const QString hourOffset = formatOffset(hourFormat, offsetMinutes, locale, width);
387 if (width == QLocale::ShortFormat)
390 QStringView offsetFormat = locData.offsetGmtFormat().viewData(gmtFormatTable);
391 Q_ASSERT(!offsetFormat.isEmpty());
392 return offsetFormat.arg(hourOffset);
397QString QTimeZonePrivate::localeName(qint64 atMSecsSinceEpoch,
int offsetFromUtc,
398 QTimeZone::TimeType timeType,
399 QTimeZone::NameType nameType,
400 const QLocale &locale)
const
402 if (nameType == QTimeZone::OffsetName) {
404 return QtTimeZoneLocale::zoneOffsetFormat(locale, locale.d->m_index, QLocale::LongFormat,
405 QDateTime(), offsetFromUtc);
409 QByteArray ianaAbbrev, ianaTail;
410 const auto scanIana = [&](QByteArrayView iana) {
414 if (!ianaAbbrev.isEmpty() && !ianaTail.isEmpty())
416 qsizetype cut = iana.lastIndexOf(
'/');
417 QByteArrayView tail = cut < 0 ? iana : iana.sliced(cut + 1);
419 if (tail ==
"McMurdo") {
420 if (ianaTail.isEmpty())
421 ianaTail =
"McMurdo"_ba;
423 }
else if (tail ==
"DumontDUrville") {
424 if (ianaTail.isEmpty())
425 ianaTail =
"Dumont d'Urville"_ba;
427 }
else if (tail.isEmpty()) {
433 bool maybeAbbr = ianaAbbrev.isEmpty(), maybeCityName = ianaTail.isEmpty(), inword =
false;
435 for (
char ch : tail) {
436 if (ch ==
'+' || ch ==
'-') {
437 if (ch ==
'+' || !inword)
438 maybeCityName =
false;
446 }
else if (ch ==
'_') {
449 maybeCityName =
false;
451 }
else if (QChar::isLower(ch)) {
455 }
else if (QChar::isUpper(ch)) {
459 maybeCityName =
false;
461 }
else if (QChar::isDigit(ch)) {
464 maybeCityName =
false;
468 if (!maybeAbbr && !maybeCityName)
471 if (maybeAbbr && maybeCityName)
475 if (tail.endsWith(
"-0") || tail.endsWith(
"+0"))
476 tail = tail.chopped(2);
477 ianaAbbrev = tail.toByteArray();
478 if (sign && iana.startsWith(
"Etc/")) {
480 ianaAbbrev = ianaAbbrev.replace(
'-',
'+');
481 else if (sign ==
'+')
482 ianaAbbrev = ianaAbbrev.replace(
'+',
'-');
486 ianaTail = tail.toByteArray().replace(
'_',
' ');
490 if (QByteArray iana = aliasToIana(m_id); !iana.isEmpty() && iana != m_id)
494#define tableLookup(table, member, sought, test)
495 findTableEntryFor(QSpan(table).first(nextData.member).sliced(locData.member), sought, test)
499 const QList<qsizetype> indices = fallbackLocalesFor(locale.d->m_index);
500 QString exemplarCity;
501 const auto metaIdBefore = [](
auto &row, quint16 key) {
return row.metaIdIndex < key; };
504 for (
const qsizetype locInd : indices) {
505 const LocaleZoneData &locData = localeZoneData[locInd];
507 Q_ASSERT(std::size_t(locInd) < std::size(localeZoneData) - 1);
508 const LocaleZoneData &nextData = localeZoneData[locInd + 1];
510 QByteArrayView iana{m_id};
511 if (quint16 metaKey = metaZoneAt(iana, atMSecsSinceEpoch)) {
512 if (
const MetaZoneData *metaFrom = metaZoneStart(metaKey)) {
513 quint16 metaIdIndex = metaFrom->metaIdIndex;
514 QLocaleData::DataRange range{0, 0};
515 const char16_t *strings =
nullptr;
516 if (nameType == QTimeZone::ShortName) {
517 auto row =
tableLookup(localeMetaZoneShortNameTable, m_metaShortTableStart,
518 metaIdIndex, metaIdBefore);
519 if (row && row->metaIdIndex == metaIdIndex) {
520 range = row->shortName(timeType);
521 strings = shortMetaZoneNameTable;
524 auto row =
tableLookup(localeMetaZoneLongNameTable, m_metaLongTableStart,
525 metaIdIndex, metaIdBefore);
526 if (row && row->metaIdIndex == metaIdIndex) {
527 range = row->longName(timeType);
528 strings = longMetaZoneNameTable;
531 Q_ASSERT(strings || !range.size);
534 return range.getData(strings);
536 if (
const auto *metaRow = metaZoneDataFor(metaFrom, locale.territory()))
537 iana = metaRow->ianaId();
542 if (exemplarCity.isEmpty()) {
543 exemplarCity = exemplarCityFor(locData, nextData, m_id);
544 if (exemplarCity.isEmpty())
545 exemplarCity = exemplarCityFor(locData, nextData, iana);
554 localeZoneNameTable, m_zoneTableStart,
555 iana, [](
auto &row, QByteArrayView key) {
return row.ianaId() < key; });
556 if (row && row->ianaId() == iana) {
557 QLocaleData::DataRange range = row->name(nameType, timeType);
559 auto table = nameType == QTimeZone::ShortName
562 return range.getData(table);
565 }
while (std::exchange(iana, QByteArrayView{m_id}) != m_id);
570 if (exemplarCity.isEmpty() && !ianaTail.isEmpty())
571 exemplarCity = QString::fromLatin1(ianaTail);
574 case QTimeZone::DefaultName:
575 case QTimeZone::LongName:
576 for (
const qsizetype locInd : indices) {
577 const LocaleZoneData &locData = localeZoneData[locInd];
578 QStringView regionFormat
579 = locData.regionFormatRange(timeType).viewData(regionFormatTable);
580 if (!regionFormat.isEmpty()) {
581 QString where = exemplarCity;
583 if (!where.isEmpty())
584 return regionFormat.arg(where);
588 for (
const qsizetype locInd : indices) {
589 const LocaleZoneData &locData = localeZoneData[locInd];
590 QStringView fallbackFormat = locData.fallbackFormat().viewData(fallbackFormatTable);
598 case QTimeZone::ShortName:
600 if (!ianaAbbrev.isEmpty())
601 return QString::fromLatin1(ianaAbbrev);
604 case QTimeZone::OffsetName:
605 Q_UNREACHABLE_RETURN(QString());
613 return QtTimeZoneLocale::zoneOffsetFormat(locale, locale.d->m_index, QLocale::NarrowFormat,
614 QDateTime(), offsetFromUtc);
QList< QByteArrayView > ianaIdsForTerritory(QLocale::Territory territory)
#define tableLookup(table, member, sought, test)