1 /*
2 * Copyright (c) 2021-2022 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include <filesystem>
17 #include <sys/stat.h>
18 #include "i18n_hilog.h"
19 #include "i18n_timezone.h"
20 #include "ohos/init_data.h"
21 #include "unicode/strenum.h"
22 #include "unicode/timezone.h"
23 #include "utils.h"
24 #include "zone_util.h"
25
26 using namespace OHOS::Global::I18n;
27 using namespace icu;
28 using namespace std;
29
30 const char *ZoneUtil::COUNTRY_ZONE_DATA_PATH = "/system/usr/ohos_timezone/tzlookup.xml";
31 const char *ZoneUtil::DISTRO_COUNTRY_ZONE_DATA_PATH = "/system/etc/tzdata_distro/hos/tzlookup.xml";
32 const char *ZoneUtil::DEFAULT_TIMEZONE = "GMT";
33 const char *ZoneUtil::TIMEZONES_TAG = "timezones";
34 const char *ZoneUtil::ID_TAG = "id";
35 const char *ZoneUtil::DEFAULT_TAG = "default";
36 const char *ZoneUtil::BOOSTED_TAG = "defaultBoost";
37 const char *ZoneUtil::ROOT_TAG = "countryzones";
38 const char *ZoneUtil::SECOND_TAG = "country";
39 const char *ZoneUtil::CODE_TAG = "code";
40 const char *ZoneUtil::TIMEZONE_KEY = "persist.time.timezone";
41 const char *ZoneUtil::TZLOOKUP_FILE_PATH = ZoneUtil::GetTZLookupDataPath();
42
43 unordered_map<string, string> ZoneUtil::defaultMap = {
44 {"AQ", "Antarctica/McMurdo"},
45 {"AR", "America/Argentina/Buenos_Aires"},
46 {"AU", "Australia/Sydney"},
47 {"BR", "America/Noronha"},
48 {"CA", "America/St_Johns"},
49 {"CD", "Africa/Kinshasa"},
50 {"CL", "America/Santiago"},
51 {"CN", "Asia/Shanghai"},
52 {"CY", "Asia/Nicosia"},
53 {"DE", "Europe/Berlin"},
54 {"EC", "America/Guayaquil"},
55 {"ES", "Europe/Madrid"},
56 {"FM", "Pacific/Pohnpei"},
57 {"GL", "America/Godthab"},
58 {"ID", "Asia/Jakarta"},
59 {"KI", "Pacific/Tarawa"},
60 {"KZ", "Asia/Almaty"},
61 {"MH", "Pacific/Majuro"},
62 {"MN", "Asia/Ulaanbaatar"},
63 {"MX", "America/Mexico_City"},
64 {"MY", "Asia/Kuala_Lumpur"},
65 {"NZ", "Pacific/Auckland"},
66 {"PF", "Pacific/Tahiti"},
67 {"PG", "Pacific/Port_Moresby"},
68 {"PS", "Asia/Gaza"},
69 {"PT", "Europe/Lisbon"},
70 {"RU", "Europe/Moscow"},
71 {"UA", "Europe/Kiev"},
72 {"UM", "Pacific/Wake"},
73 {"US", "America/New_York"},
74 {"UZ", "Asia/Tashkent"},
75 };
76
77 bool ZoneUtil::icuInitialized = ZoneUtil::Init();
78
GetDefaultZone(const string &country)79 string ZoneUtil::GetDefaultZone(const string &country)
80 {
81 string temp(country);
82 for (size_t i = 0; i < temp.size(); i++) {
83 temp[i] = (char)toupper(temp[i]);
84 }
85 if (defaultMap.find(temp) != defaultMap.end()) {
86 return defaultMap[temp];
87 }
88 string ret;
89 StringEnumeration *strEnum = TimeZone::createEnumeration(temp.c_str());
90 GetString(strEnum, ret);
91 if (strEnum != nullptr) {
92 delete strEnum;
93 }
94 return ret;
95 }
96
GetDefaultZone(const int32_t number)97 string ZoneUtil::GetDefaultZone(const int32_t number)
98 {
99 using i18n::phonenumbers::PhoneNumberUtil;
100 string *region_code = new(nothrow) string();
101 if (!region_code) {
102 return "";
103 }
104 PhoneNumberUtil* phoneUtil = PhoneNumberUtil::GetInstance();
105 phoneUtil->GetRegionCodeForCountryCode(number, region_code);
106 if (!region_code) {
107 return "";
108 }
109 string ret = GetDefaultZone(*region_code);
110 delete region_code;
111 return ret;
112 }
113
GetDefaultZone(const string country, const int32_t offset)114 string ZoneUtil::GetDefaultZone(const string country, const int32_t offset)
115 {
116 UErrorCode status = U_ZERO_ERROR;
117 StringEnumeration *strEnum =
118 TimeZone::createTimeZoneIDEnumeration(UCAL_ZONE_TYPE_ANY, country.c_str(), &offset, status);
119 if (U_FAILURE(status)) {
120 return "";
121 }
122 string ret;
123 GetString(strEnum, ret);
124 if (strEnum != nullptr) {
125 delete strEnum;
126 strEnum = nullptr;
127 }
128 return ret;
129 }
130
GetDefaultZone(const int32_t number, const int32_t offset)131 string ZoneUtil::GetDefaultZone(const int32_t number, const int32_t offset)
132 {
133 using i18n::phonenumbers::PhoneNumberUtil;
134 string *region_code = new(nothrow) string();
135 if (!region_code) {
136 return "";
137 }
138 PhoneNumberUtil* phoneUtil = PhoneNumberUtil::GetInstance();
139 phoneUtil->GetRegionCodeForCountryCode(number, region_code);
140 if (!region_code) {
141 return "";
142 }
143 string ret = GetDefaultZone(*region_code, offset);
144 delete region_code;
145 return ret;
146 }
147
GetZoneList(const string country, vector<string> &retVec)148 void ZoneUtil::GetZoneList(const string country, vector<string> &retVec)
149 {
150 StringEnumeration *strEnum = TimeZone::createEnumeration(country.c_str());
151 GetList(strEnum, retVec);
152 if (strEnum != nullptr) {
153 delete strEnum;
154 strEnum = nullptr;
155 }
156 }
157
GetZoneList(const string country, const int32_t offset, vector<string> &retVec)158 void ZoneUtil::GetZoneList(const string country, const int32_t offset, vector<string> &retVec)
159 {
160 UErrorCode status = U_ZERO_ERROR;
161 StringEnumeration *strEnum =
162 TimeZone::createTimeZoneIDEnumeration(UCAL_ZONE_TYPE_ANY, country.c_str(), &offset, status);
163 if (U_FAILURE(status)) {
164 delete strEnum;
165 strEnum = nullptr;
166 return;
167 }
168 GetList(strEnum, retVec);
169 if (strEnum != nullptr) {
170 delete strEnum;
171 strEnum = nullptr;
172 }
173 }
174
GetString(StringEnumeration *strEnum, string& ret)175 void ZoneUtil::GetString(StringEnumeration *strEnum, string& ret)
176 {
177 UErrorCode status = U_ZERO_ERROR;
178 UnicodeString uniString;
179 if (!strEnum) {
180 return;
181 }
182 int32_t count = strEnum->count(status);
183 if (U_FAILURE(status) || count <= 0) {
184 return;
185 }
186 const UnicodeString *uniStr = strEnum->snext(status);
187 if (U_FAILURE(status) || (!uniStr)) {
188 return;
189 }
190 UnicodeString canonicalUnistring;
191 TimeZone::getCanonicalID(*uniStr, canonicalUnistring, status);
192 if (U_FAILURE(status)) {
193 return;
194 }
195 canonicalUnistring.toUTF8String(ret);
196 return;
197 }
198
GetList(StringEnumeration *strEnum, vector<string> &retVec)199 void ZoneUtil::GetList(StringEnumeration *strEnum, vector<string> &retVec)
200 {
201 if (!strEnum) {
202 return;
203 }
204 UErrorCode status = U_ZERO_ERROR;
205 int32_t count = strEnum->count(status);
206 if (count <= 0 || U_FAILURE(status)) {
207 return;
208 }
209 while (count > 0) {
210 const UnicodeString *uniStr = strEnum->snext(status);
211 if ((!uniStr) || U_FAILURE(status)) {
212 retVec.clear();
213 break;
214 }
215 UnicodeString canonicalUnistring;
216 TimeZone::getCanonicalID(*uniStr, canonicalUnistring, status);
217 if (U_FAILURE(status)) {
218 retVec.clear();
219 break;
220 }
221 string canonicalString = "";
222 canonicalUnistring.toUTF8String(canonicalString);
223 if ((canonicalString != "") && (find(retVec.begin(), retVec.end(), canonicalString) == retVec.end())) {
224 retVec.push_back(canonicalString);
225 }
226 --count;
227 }
228 return;
229 }
230
Init()231 bool ZoneUtil::Init()
232 {
233 SetHwIcuDirectory();
234 return true;
235 }
236
LookupTimezoneByCountryAndNITZ(std::string ®ion, NITZData &nitzData)237 CountryResult ZoneUtil::LookupTimezoneByCountryAndNITZ(std::string ®ion, NITZData &nitzData)
238 {
239 std::vector<std::string> zones;
240 std::string defaultTimezone;
241 std::string systemTimezone = ReadSystemParameter(TIMEZONE_KEY, SYS_PARAM_LEN);
242 if (systemTimezone.length() == 0) {
243 systemTimezone = DEFAULT_TIMEZONE;
244 }
245 if (TZLOOKUP_FILE_PATH != nullptr) {
246 bool isBoosted = false;
247 HILOG_INFO_I18N("ZoneUtil::LookupTimezoneByCountryAndNITZ use tzlookup.xml");
248 GetCountryZones(region, defaultTimezone, isBoosted, zones);
249 } else {
250 HILOG_INFO_I18N("ZoneUtil::LookupTimezoneByCountryAndNITZ use icu data");
251 GetICUCountryZones(region, zones, defaultTimezone);
252 }
253 return Match(zones, nitzData, systemTimezone);
254 }
255
LookupTimezoneByNITZ(NITZData &nitzData)256 CountryResult ZoneUtil::LookupTimezoneByNITZ(NITZData &nitzData)
257 {
258 std::string systemTimezone = ReadSystemParameter(TIMEZONE_KEY, SYS_PARAM_LEN);
259 if (systemTimezone.length() == 0) {
260 systemTimezone = DEFAULT_TIMEZONE;
261 }
262 I18nErrorCode status = I18nErrorCode::SUCCESS;
263 std::set<std::string> icuTimezones = I18nTimeZone::GetAvailableIDs(status);
264 if (status != I18nErrorCode::SUCCESS) {
265 HILOG_ERROR_I18N("ZoneUtil::LookupTimezoneByNITZ can not get icu data");
266 }
267 std::vector<std::string> validZones;
268 for (auto it = icuTimezones.begin(); it != icuTimezones.end(); ++it) {
269 validZones.push_back(*it);
270 }
271
272 CountryResult result = Match(validZones, nitzData, systemTimezone);
273 if (result.timezoneId.length() == 0 && nitzData.isDST >= 0) {
274 NITZData newNITZData = { -1, nitzData.totalOffset, nitzData.currentMillis }; // -1 means not consider DST
275 result = Match(validZones, newNITZData, systemTimezone);
276 }
277 return result;
278 }
279
LookupTimezoneByCountry(std::string ®ion, int64_t currentMillis)280 CountryResult ZoneUtil::LookupTimezoneByCountry(std::string ®ion, int64_t currentMillis)
281 {
282 std::vector<std::string> zones;
283 bool isBoosted = false;
284 std::string defaultTimezone;
285 CountryResult result = { true, MatchQuality::DEFAULT_BOOSTED, defaultTimezone };
286 if (TZLOOKUP_FILE_PATH != nullptr) {
287 HILOG_INFO_I18N("ZoneUtil::LookupTimezoneByCountry use tzlookup.xml");
288 GetCountryZones(region, defaultTimezone, isBoosted, zones);
289 if (defaultTimezone.length() == 0) {
290 HILOG_ERROR_I18N("ZoneUtil::LookupTimezoneByCountry can't find default timezone for region %{public}s",
291 region.c_str());
292 }
293 } else {
294 HILOG_INFO_I18N("ZoneUtil::LookupTimezoneByCountry use icu data");
295 GetICUCountryZones(region, zones, defaultTimezone);
296 }
297 result.timezoneId = defaultTimezone;
298 if (isBoosted) {
299 return result;
300 }
301 if (zones.size() == 0) {
302 result.quality = MatchQuality::MULTIPLE_ZONES_DIFFERENT_OFFSET;
303 } else if (zones.size() == 1) {
304 result.quality = MatchQuality::SINGLE_ZONE;
305 } else if (CheckSameDstOffset(zones, defaultTimezone, currentMillis)) {
306 result.quality = MatchQuality::MULTIPLE_ZONES_SAME_OFFSET;
307 } else {
308 result.quality = MatchQuality::MULTIPLE_ZONES_DIFFERENT_OFFSET;
309 }
310 return result;
311 }
312
GetTZLookupDataPath()313 const char *ZoneUtil::GetTZLookupDataPath()
314 {
315 using std::filesystem::directory_iterator;
316 struct stat s;
317 if (stat(DISTRO_COUNTRY_ZONE_DATA_PATH, &s) == 0) {
318 return DISTRO_COUNTRY_ZONE_DATA_PATH;
319 }
320 if (stat(COUNTRY_ZONE_DATA_PATH, &s) == 0) {
321 return COUNTRY_ZONE_DATA_PATH;
322 } else {
323 return nullptr;
324 }
325 }
326
GetCountryZones(std::string ®ion, std::string &defaultTimzone, bool &isBoosted, std::vector<std::string> &zones)327 void ZoneUtil::GetCountryZones(std::string ®ion, std::string &defaultTimzone, bool &isBoosted,
328 std::vector<std::string> &zones)
329 {
330 xmlKeepBlanksDefault(0);
331 xmlDocPtr doc = xmlParseFile(TZLOOKUP_FILE_PATH);
332 if (!doc) {
333 HILOG_ERROR_I18N("ZoneUtil::GetCountryZones can not open tzlookup.xml");
334 return;
335 }
336 xmlNodePtr cur = xmlDocGetRootElement(doc);
337 if (!cur || xmlStrcmp(cur->name, reinterpret_cast<const xmlChar *>(ROOT_TAG)) != 0) {
338 xmlFreeDoc(doc);
339 HILOG_ERROR_I18N("ZoneUtil::GetCountryZones invalid Root_tag");
340 return;
341 }
342 cur = cur->xmlChildrenNode;
343 xmlNodePtr value;
344 bool findCountry = false;
345 while (cur != nullptr && xmlStrcmp(cur->name, reinterpret_cast<const xmlChar *>(SECOND_TAG)) == 0) {
346 value = cur->xmlChildrenNode;
347 if (xmlStrcmp(value->name, reinterpret_cast<const xmlChar*>(CODE_TAG)) != 0) {
348 xmlFreeDoc(doc);
349 HILOG_ERROR_I18N("ZoneUtil::GetCountryZones invalid code_tag");
350 return;
351 }
352 xmlChar *codePtr = xmlNodeGetContent(value);
353 if (codePtr == nullptr) {
354 cur = cur->next;
355 continue;
356 }
357 if (strcmp(region.c_str(), reinterpret_cast<const char*>(codePtr)) == 0) {
358 findCountry = true;
359 xmlFree(codePtr);
360 break;
361 } else {
362 xmlFree(codePtr);
363 cur = cur->next;
364 continue;
365 }
366 }
367 if (findCountry) {
368 value = value->next;
369 GetDefaultAndBoost(value, defaultTimzone, isBoosted, zones);
370 }
371 xmlFreeDoc(doc);
372 return;
373 }
374
GetDefaultAndBoost(xmlNodePtr &value, std::string &defaultTimezone, bool &isBoosted, std::vector<std::string> &zones)375 void ZoneUtil::GetDefaultAndBoost(xmlNodePtr &value, std::string &defaultTimezone, bool &isBoosted,
376 std::vector<std::string> &zones)
377 {
378 if (value == nullptr || xmlStrcmp(value->name, reinterpret_cast<const xmlChar*>(DEFAULT_TAG)) != 0) {
379 HILOG_ERROR_I18N("ZoneUtil::GetDefaultAndBoost invalid default_tag");
380 return;
381 }
382 xmlChar *defaultPtr = xmlNodeGetContent(value);
383 if (defaultPtr != nullptr) {
384 defaultTimezone = reinterpret_cast<const char*>(defaultPtr);
385 xmlFree(defaultPtr);
386 }
387 value = value->next;
388 if (value == nullptr) {
389 HILOG_ERROR_I18N("ZoneUtil::GetDefaultAndBoost doesn't contains id");
390 return;
391 }
392 if (xmlStrcmp(value->name, reinterpret_cast<const xmlChar *>(BOOSTED_TAG)) == 0) {
393 isBoosted = true;
394 value = value->next;
395 } else {
396 isBoosted = false;
397 }
398 GetTimezones(value, zones);
399 }
400
GetTimezones(xmlNodePtr &value, std::vector<std::string> &zones)401 void ZoneUtil::GetTimezones(xmlNodePtr &value, std::vector<std::string> &zones)
402 {
403 if (xmlStrcmp(value->name, reinterpret_cast<const xmlChar *>(TIMEZONES_TAG)) != 0) {
404 HILOG_ERROR_I18N("ZoneUtil::GetTimezones invalid timezones_tag");
405 return;
406 }
407 value = value->xmlChildrenNode;
408 while (value != nullptr) {
409 if (xmlStrcmp(value->name, reinterpret_cast<const xmlChar *>(ID_TAG)) != 0) {
410 HILOG_ERROR_I18N("ZoneUtil::GetTimezones invalid id_tag");
411 return;
412 }
413 xmlChar *idPtr = xmlNodeGetContent(value);
414 if (idPtr != nullptr) {
415 zones.push_back(reinterpret_cast<const char*>(idPtr));
416 xmlFree(idPtr);
417 }
418 value = value->next;
419 }
420 }
421
GetICUCountryZones(std::string ®ion, std::vector<std::string> &zones, std::string &defaultTimezone)422 void ZoneUtil::GetICUCountryZones(std::string ®ion, std::vector<std::string> &zones, std::string &defaultTimezone)
423 {
424 I18nErrorCode errorCode = I18nErrorCode::SUCCESS;
425 std::set<std::string> validZoneIds = I18nTimeZone::GetAvailableIDs(errorCode);
426 if (errorCode != I18nErrorCode::SUCCESS) {
427 HILOG_ERROR_I18N("ZoneUtil::GetICUCountryZones can not get icu data");
428 }
429 std::set<std::string> countryZoneIds;
430 StringEnumeration *strEnum = TimeZone::createEnumeration(region.c_str());
431 UErrorCode status = U_ZERO_ERROR;
432 const UnicodeString *timezoneIdUStr = strEnum->snext(status);
433 while (timezoneIdUStr != nullptr && U_SUCCESS(status)) {
434 UnicodeString canonicalUnistring;
435 TimeZone::getCanonicalID(*timezoneIdUStr, canonicalUnistring, status);
436 std::string timezoneId;
437 canonicalUnistring.toUTF8String(timezoneId);
438 if (validZoneIds.find(timezoneId) != validZoneIds.end()) {
439 countryZoneIds.insert(timezoneId);
440 }
441 timezoneIdUStr = strEnum->snext(status);
442 }
443 for (auto it = countryZoneIds.begin(); it != countryZoneIds.end(); ++it) {
444 zones.push_back(*it);
445 }
446 if (defaultMap.find(region) != defaultMap.end()) {
447 defaultTimezone = defaultMap[region];
448 } else {
449 if (zones.size() > 0) {
450 defaultTimezone = zones[0];
451 }
452 }
453 }
454
Match(std::vector<std::string> &zones, NITZData &nitzData, std::string &systemTimezone)455 CountryResult ZoneUtil::Match(std::vector<std::string> &zones, NITZData &nitzData, std::string &systemTimezone)
456 {
457 bool isOnlyMatch = true;
458 std::string matchedZoneId;
459 bool local = false;
460 bool useSystemTimezone = false;
461 for (size_t i = 0; i < zones.size(); i++) {
462 std::string zoneId = zones[i];
463 UnicodeString unicodeZoneID(zoneId.data(), zoneId.length());
464 TimeZone *timezone = TimeZone::createTimeZone(unicodeZoneID);
465 int32_t rawOffset;
466 int32_t dstOffset;
467 UErrorCode status = UErrorCode::U_ZERO_ERROR;
468 timezone->getOffset(nitzData.currentMillis, static_cast<UBool>(local), rawOffset, dstOffset, status);
469 if ((nitzData.totalOffset - rawOffset == dstOffset) &&
470 (nitzData.isDST < 0 || nitzData.isDST == (dstOffset != 0))) {
471 if (matchedZoneId.length() == 0) {
472 matchedZoneId = zoneId;
473 } else {
474 isOnlyMatch = false;
475 }
476 if (strcmp(zoneId.c_str(), systemTimezone.c_str()) == 0) {
477 matchedZoneId = systemTimezone;
478 useSystemTimezone = true;
479 }
480 if (!isOnlyMatch && useSystemTimezone) {
481 break;
482 }
483 }
484 }
485 CountryResult result = {isOnlyMatch, MatchQuality::DEFAULT_BOOSTED, matchedZoneId};
486 return result;
487 }
488
CheckSameDstOffset(std::vector<std::string> &zones, std::string &defaultTimezoneId, int64_t currentMillis)489 bool ZoneUtil::CheckSameDstOffset(std::vector<std::string> &zones, std::string &defaultTimezoneId,
490 int64_t currentMillis)
491 {
492 UnicodeString defaultID(defaultTimezoneId.data(), defaultTimezoneId.length());
493 TimeZone *defaultTimezone = TimeZone::createTimeZone(defaultID);
494 int32_t rawOffset = 0;
495 int32_t dstOffset = 0;
496 bool local = false;
497 UErrorCode status = U_ZERO_ERROR;
498 defaultTimezone->getOffset(currentMillis, (UBool)local, rawOffset, dstOffset, status);
499 if (U_FAILURE(status)) {
500 HILOG_ERROR_I18N("ZoneUtil::CheckSameDstOffset can not get timezone defaultID offset");
501 return false;
502 }
503 int32_t totalOffset = rawOffset + dstOffset;
504 for (size_t i = 0; i < zones.size(); i++) {
505 UnicodeString unicodeZoneID(zones[i].data(), zones[i].length());
506 TimeZone *timezone = TimeZone::createTimeZone(unicodeZoneID);
507 timezone->getOffset(currentMillis, (UBool)local, rawOffset, dstOffset, status);
508 if (U_FAILURE(status)) {
509 HILOG_ERROR_I18N("ZoneUtil::CheckSameDstOffset can not get timezone unicodeZoneID offset");
510 return false;
511 }
512 if (totalOffset - rawOffset != dstOffset) {
513 return false;
514 }
515 }
516 return true;
517 }
518