1/*
2 SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6
7#include "kcountrysubdivision.h"
8#include "isocodes_p.h"
9#include "isocodescache_p.h"
10#include "kcountry.h"
11#include "ki18n_logging.h"
12#include "klocalizedstring.h"
13#include "spatial_index_p.h"
14#include "timezonedata_p.h"
15
16#include <cstring>
17
18static_assert(sizeof(KCountrySubdivision) == 4);
19
20KCountrySubdivision::KCountrySubdivision()
21 : d(0)
22{
23}
24
25KCountrySubdivision::KCountrySubdivision(const KCountrySubdivision &) = default;
26KCountrySubdivision::~KCountrySubdivision() = default;
27KCountrySubdivision &KCountrySubdivision::operator=(const KCountrySubdivision &) = default;
28
29bool KCountrySubdivision::operator==(const KCountrySubdivision &other) const
30{
31 return d == other.d;
32}
33
34bool KCountrySubdivision::operator!=(const KCountrySubdivision &other) const
35{
36 return d != other.d;
37}
38
39bool KCountrySubdivision::isValid() const
40{
41 return d != 0;
42}
43
44QString KCountrySubdivision::code() const
45{
46 if (d == 0) {
47 return {};
48 }
49
50 QString code;
51 code.reserve(asize: 6);
52 code += country().alpha2();
53 code += QLatin1Char('-');
54
55 auto key = d & 0xffff;
56 while (key) {
57 const auto c = IsoCodes::mapFromAlphaNumKey(key);
58 if (c) {
59 code.insert(i: 3, c: QLatin1Char(c));
60 }
61 key /= IsoCodes::AlphaNumKeyFactor;
62 }
63
64 return code;
65}
66
67QString KCountrySubdivision::name() const
68{
69 if (d == 0) {
70 return {};
71 }
72
73 auto cache = IsoCodesCache::instance();
74 cache->loadIso3166_2();
75 const auto it = std::lower_bound(first: cache->subdivisionNameMapBegin(), last: cache->subdivisionNameMapEnd(), val: d);
76 if (it != cache->subdivisionNameMapEnd() && (*it).key == d) {
77 return i18nd(domain: "iso_3166-2", text: cache->subdivisionStringTableLookup(offset: (*it).value));
78 }
79 return {};
80}
81
82KCountry KCountrySubdivision::country() const
83{
84 KCountry c;
85 c.d = d >> 16;
86 return c;
87}
88
89KCountrySubdivision KCountrySubdivision::parent() const
90{
91 KCountrySubdivision s;
92 if (d == 0) {
93 return s;
94 }
95
96 auto cache = IsoCodesCache::instance();
97 cache->loadIso3166_2();
98
99 const auto it = std::lower_bound(first: cache->subdivisionParentMapBegin(), last: cache->subdivisionParentMapEnd(), val: d);
100 if (it != cache->subdivisionParentMapEnd() && (*it).key == d) {
101 s.d = (d & 0xffff0000) | (uint32_t)(*it).value;
102 }
103
104 return s;
105}
106
107QList<const char *> KCountrySubdivision::timeZoneIds() const
108{
109 QList<const char *> tzs;
110 if (d == 0) {
111 return tzs;
112 }
113
114 const auto [subdivBegin, subdivEnd] = std::equal_range(first: TimezoneData::subdivisionTimezoneMapBegin(), last: TimezoneData::subdivisionTimezoneMapEnd(), val: d);
115 if (subdivBegin != subdivEnd) {
116 tzs.reserve(asize: std::distance(first: subdivBegin, last: subdivEnd));
117 for (auto it = subdivBegin; it != subdivEnd; ++it) {
118 tzs.push_back(t: TimezoneData::ianaIdLookup(offset: (*it).value));
119 }
120 return tzs;
121 }
122
123 const auto countryIt = std::lower_bound(first: TimezoneData::countryTimezoneMapBegin(), last: TimezoneData::countryTimezoneMapEnd(), val: uint16_t(d >> 16));
124 if (countryIt != TimezoneData::countryTimezoneMapEnd() && (*countryIt).key == (d >> 16)) {
125 tzs.push_back(t: TimezoneData::ianaIdLookup(offset: (*countryIt).value));
126 }
127
128 return tzs;
129}
130
131QList<KCountrySubdivision> KCountrySubdivision::subdivisions() const
132{
133 if (d == 0) {
134 return {};
135 }
136
137 QList<KCountrySubdivision> l;
138 auto cache = IsoCodesCache::instance();
139 cache->loadIso3166_2();
140 // we don't have a parent->child map, instead we use the child->parent map
141 // that is sorted by country (due to the country being in the two most significant bytes of its key),
142 // so we don't need to do a full sequential search here
143 auto it = std::lower_bound(first: cache->subdivisionParentMapBegin(), last: cache->subdivisionParentMapEnd(), val: d >> 16, comp: [](auto lhs, auto rhs) {
144 return (lhs.key >> 16) < rhs;
145 });
146 for (; it != cache->subdivisionParentMapEnd() && ((*it).key >> 16) == (d >> 16); ++it) {
147 if ((*it).value == (d & 0xffff)) {
148 KCountrySubdivision s;
149 s.d = (*it).key;
150 l.push_back(t: s);
151 }
152 }
153
154 return l;
155}
156
157static uint32_t validatedSubdivisionKey(uint32_t key)
158{
159 if (!key) {
160 return 0;
161 }
162
163 auto cache = IsoCodesCache::instance();
164 cache->loadIso3166_2();
165
166 const auto it = std::lower_bound(first: cache->subdivisionNameMapBegin(), last: cache->subdivisionNameMapEnd(), val: key);
167 if (it != cache->subdivisionNameMapEnd() && (*it).key == key) {
168 return key;
169 }
170 return 0;
171}
172
173KCountrySubdivision KCountrySubdivision::fromCode(QStringView code)
174{
175 KCountrySubdivision s;
176 s.d = validatedSubdivisionKey(key: IsoCodes::subdivisionCodeToKey(code));
177 return s;
178}
179
180KCountrySubdivision KCountrySubdivision::fromCode(const char *code)
181{
182 KCountrySubdivision s;
183 if (code) {
184 s.d = validatedSubdivisionKey(key: IsoCodes::subdivisionCodeToKey(code, size: std::strlen(s: code)));
185 }
186 return s;
187}
188
189KCountrySubdivision KCountrySubdivision::fromLocation(float latitude, float longitude)
190{
191 const auto entry = SpatialIndex::lookup(lat: latitude, lon: longitude);
192 KCountrySubdivision s;
193 if (entry.m_subdiv & 0xffff) {
194 s.d = entry.m_subdiv;
195 }
196 return s;
197}
198
199QStringList KCountrySubdivision::timeZoneIdsStringList() const
200{
201 const auto tzIds = timeZoneIds();
202 QStringList l;
203 l.reserve(asize: tzIds.size());
204 std::transform(first: tzIds.begin(), last: tzIds.end(), result: std::back_inserter(x&: l), unary_op: [](const char *tzId) {
205 return QString::fromUtf8(utf8: tzId);
206 });
207 return l;
208}
209
210#include "moc_kcountrysubdivision.cpp"
211

source code of ki18n/src/localedata/kcountrysubdivision.cpp