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 {"BT", "Asia/Thimbu"}, // alias Asia/Thimphu
49 {"CA", "America/St_Johns"},
50 {"CD", "Africa/Kinshasa"},
51 {"CL", "America/Santiago"},
52 {"CN", "Asia/Shanghai"},
53 {"CY", "Asia/Nicosia"},
54 {"DE", "Europe/Berlin"},
55 {"DG", "Indian/Chagos"},
56 {"EA", "Africa/Ceuta"},
57 {"EC", "America/Guayaquil"},
58 {"ER", "Africa/Asmara"}, // alias Africa/Asmera
59 {"ES", "Europe/Madrid"},
60 {"FM", "Pacific/Pohnpei"},
61 {"FO", "Atlantic/Faroe"}, // alias Atlantic/Faeroe
62 {"GL", "America/Godthab"},
63 {"IC", "Atlantic/Canary"},
64 {"ID", "Asia/Jakarta"},
65 {"IN", "Asia/Kolkata"}, // alias Asia/Calcutta
66 {"KI", "Pacific/Tarawa"},
67 {"KZ", "Asia/Almaty"},
68 {"MH", "Pacific/Majuro"},
69 {"MN", "Asia/Ulaanbaatar"},
70 {"MX", "America/Mexico_City"},
71 {"MY", "Asia/Kuala_Lumpur"},
72 {"NP", "Asia/Kathmandu"}, // alias Asia/Katmandu
73 {"NZ", "Pacific/Auckland"},
74 {"PF", "Pacific/Tahiti"},
75 {"PG", "Pacific/Port_Moresby"},
76 {"PS", "Asia/Gaza"},
77 {"PT", "Europe/Lisbon"},
78 {"RU", "Europe/Moscow"},
79 {"UA", "Europe/Kiev"},
80 {"UM", "Pacific/Wake"},
81 {"US", "America/New_York"},
82 {"UZ", "Asia/Tashkent"},
83 {"VN", "Asia/Ho_Chi_Minh"}, // alias Asia/Saigon
84 };
85
86 bool ZoneUtil::icuInitialized = ZoneUtil::Init();
87
GetDefaultZone(const string & country)88 string ZoneUtil::GetDefaultZone(const string &country)
89 {
90 string temp(country);
91 for (size_t i = 0; i < temp.size(); i++) {
92 temp[i] = (char)toupper(temp[i]);
93 }
94 if (defaultMap.find(temp) != defaultMap.end()) {
95 return defaultMap[temp];
96 }
97 string ret;
98 StringEnumeration *strEnum = TimeZone::createEnumeration(temp.c_str());
99 GetString(strEnum, ret);
100 if (strEnum != nullptr) {
101 delete strEnum;
102 }
103 return ret;
104 }
105
GetDefaultZone(const int32_t number)106 string ZoneUtil::GetDefaultZone(const int32_t number)
107 {
108 using i18n::phonenumbers::PhoneNumberUtil;
109 string *region_code = new(nothrow) string();
110 if (!region_code) {
111 return "";
112 }
113 PhoneNumberUtil* phoneUtil = PhoneNumberUtil::GetInstance();
114 phoneUtil->GetRegionCodeForCountryCode(number, region_code);
115 if (!region_code) {
116 return "";
117 }
118 string ret = GetDefaultZone(*region_code);
119 delete region_code;
120 return ret;
121 }
122
GetDefaultZone(const string country,const int32_t offset)123 string ZoneUtil::GetDefaultZone(const string country, const int32_t offset)
124 {
125 UErrorCode status = U_ZERO_ERROR;
126 StringEnumeration *strEnum =
127 TimeZone::createTimeZoneIDEnumeration(UCAL_ZONE_TYPE_ANY, country.c_str(), &offset, status);
128 if (U_FAILURE(status)) {
129 return "";
130 }
131 string ret;
132 GetString(strEnum, ret);
133 if (strEnum != nullptr) {
134 delete strEnum;
135 strEnum = nullptr;
136 }
137 return ret;
138 }
139
GetDefaultZone(const int32_t number,const int32_t offset)140 string ZoneUtil::GetDefaultZone(const int32_t number, const int32_t offset)
141 {
142 using i18n::phonenumbers::PhoneNumberUtil;
143 string *region_code = new(nothrow) string();
144 if (!region_code) {
145 return "";
146 }
147 PhoneNumberUtil* phoneUtil = PhoneNumberUtil::GetInstance();
148 phoneUtil->GetRegionCodeForCountryCode(number, region_code);
149 if (!region_code) {
150 return "";
151 }
152 string ret = GetDefaultZone(*region_code, offset);
153 delete region_code;
154 return ret;
155 }
156
GetZoneList(const string country,vector<string> & retVec)157 void ZoneUtil::GetZoneList(const string country, vector<string> &retVec)
158 {
159 StringEnumeration *strEnum = TimeZone::createEnumeration(country.c_str());
160 GetList(strEnum, retVec);
161 if (strEnum != nullptr) {
162 delete strEnum;
163 strEnum = nullptr;
164 }
165 }
166
GetZoneList(const string country,const int32_t offset,vector<string> & retVec)167 void ZoneUtil::GetZoneList(const string country, const int32_t offset, vector<string> &retVec)
168 {
169 UErrorCode status = U_ZERO_ERROR;
170 StringEnumeration *strEnum =
171 TimeZone::createTimeZoneIDEnumeration(UCAL_ZONE_TYPE_ANY, country.c_str(), &offset, status);
172 if (U_FAILURE(status)) {
173 delete strEnum;
174 strEnum = nullptr;
175 return;
176 }
177 GetList(strEnum, retVec);
178 if (strEnum != nullptr) {
179 delete strEnum;
180 strEnum = nullptr;
181 }
182 }
183
GetString(StringEnumeration * strEnum,string & ret)184 void ZoneUtil::GetString(StringEnumeration *strEnum, string& ret)
185 {
186 UErrorCode status = U_ZERO_ERROR;
187 UnicodeString uniString;
188 if (!strEnum) {
189 return;
190 }
191 int32_t count = strEnum->count(status);
192 if (U_FAILURE(status) || count <= 0) {
193 return;
194 }
195 const UnicodeString *uniStr = strEnum->snext(status);
196 if (U_FAILURE(status) || (!uniStr)) {
197 return;
198 }
199 UnicodeString canonicalUnistring;
200 TimeZone::getCanonicalID(*uniStr, canonicalUnistring, status);
201 if (U_FAILURE(status)) {
202 return;
203 }
204 canonicalUnistring.toUTF8String(ret);
205 return;
206 }
207
GetList(StringEnumeration * strEnum,vector<string> & retVec)208 void ZoneUtil::GetList(StringEnumeration *strEnum, vector<string> &retVec)
209 {
210 if (!strEnum) {
211 return;
212 }
213 UErrorCode status = U_ZERO_ERROR;
214 int32_t count = strEnum->count(status);
215 if (count <= 0 || U_FAILURE(status)) {
216 return;
217 }
218 while (count > 0) {
219 const UnicodeString *uniStr = strEnum->snext(status);
220 if ((!uniStr) || U_FAILURE(status)) {
221 retVec.clear();
222 break;
223 }
224 UnicodeString canonicalUnistring;
225 TimeZone::getCanonicalID(*uniStr, canonicalUnistring, status);
226 if (U_FAILURE(status)) {
227 retVec.clear();
228 break;
229 }
230 string canonicalString = "";
231 canonicalUnistring.toUTF8String(canonicalString);
232 if ((canonicalString != "") && (find(retVec.begin(), retVec.end(), canonicalString) == retVec.end())) {
233 retVec.push_back(canonicalString);
234 }
235 --count;
236 }
237 return;
238 }
239
Init()240 bool ZoneUtil::Init()
241 {
242 SetHwIcuDirectory();
243 return true;
244 }
245
LookupTimezoneByCountryAndNITZ(std::string & region,NITZData & nitzData)246 CountryResult ZoneUtil::LookupTimezoneByCountryAndNITZ(std::string ®ion, NITZData &nitzData)
247 {
248 std::vector<std::string> zones;
249 std::string defaultTimezone;
250 std::string systemTimezone = ReadSystemParameter(TIMEZONE_KEY, SYS_PARAM_LEN);
251 if (systemTimezone.length() == 0) {
252 systemTimezone = DEFAULT_TIMEZONE;
253 }
254 if (TZLOOKUP_FILE_PATH != nullptr) {
255 bool isBoosted = false;
256 HILOG_INFO_I18N("ZoneUtil::LookupTimezoneByCountryAndNITZ use tzlookup.xml");
257 GetCountryZones(region, defaultTimezone, isBoosted, zones);
258 } else {
259 HILOG_INFO_I18N("ZoneUtil::LookupTimezoneByCountryAndNITZ use icu data");
260 GetICUCountryZones(region, zones, defaultTimezone);
261 }
262 return Match(zones, nitzData, systemTimezone);
263 }
264
LookupTimezoneByNITZ(NITZData & nitzData)265 CountryResult ZoneUtil::LookupTimezoneByNITZ(NITZData &nitzData)
266 {
267 std::string systemTimezone = ReadSystemParameter(TIMEZONE_KEY, SYS_PARAM_LEN);
268 if (systemTimezone.length() == 0) {
269 systemTimezone = DEFAULT_TIMEZONE;
270 }
271 I18nErrorCode status = I18nErrorCode::SUCCESS;
272 std::set<std::string> icuTimezones = I18nTimeZone::GetAvailableIDs(status);
273 if (status != I18nErrorCode::SUCCESS) {
274 HILOG_ERROR_I18N("ZoneUtil::LookupTimezoneByNITZ can not get icu data");
275 }
276 std::vector<std::string> validZones;
277 for (auto it = icuTimezones.begin(); it != icuTimezones.end(); ++it) {
278 validZones.push_back(*it);
279 }
280
281 CountryResult result = Match(validZones, nitzData, systemTimezone);
282 if (result.timezoneId.length() == 0 && nitzData.isDST >= 0) {
283 NITZData newNITZData = { -1, nitzData.totalOffset, nitzData.currentMillis }; // -1 means not consider DST
284 result = Match(validZones, newNITZData, systemTimezone);
285 }
286 return result;
287 }
288
LookupTimezoneByCountry(std::string & region,int64_t currentMillis)289 CountryResult ZoneUtil::LookupTimezoneByCountry(std::string ®ion, int64_t currentMillis)
290 {
291 std::vector<std::string> zones;
292 bool isBoosted = false;
293 std::string defaultTimezone;
294 CountryResult result = { true, MatchQuality::DEFAULT_BOOSTED, defaultTimezone };
295 if (TZLOOKUP_FILE_PATH != nullptr) {
296 HILOG_INFO_I18N("ZoneUtil::LookupTimezoneByCountry use tzlookup.xml");
297 GetCountryZones(region, defaultTimezone, isBoosted, zones);
298 if (defaultTimezone.length() == 0) {
299 HILOG_ERROR_I18N("ZoneUtil::LookupTimezoneByCountry can't find default timezone for region %{public}s",
300 region.c_str());
301 }
302 } else {
303 HILOG_INFO_I18N("ZoneUtil::LookupTimezoneByCountry use icu data");
304 GetICUCountryZones(region, zones, defaultTimezone);
305 }
306 result.timezoneId = defaultTimezone;
307 if (isBoosted) {
308 return result;
309 }
310 if (zones.size() == 0) {
311 result.quality = MatchQuality::MULTIPLE_ZONES_DIFFERENT_OFFSET;
312 } else if (zones.size() == 1) {
313 result.quality = MatchQuality::SINGLE_ZONE;
314 } else if (CheckSameDstOffset(zones, defaultTimezone, currentMillis)) {
315 result.quality = MatchQuality::MULTIPLE_ZONES_SAME_OFFSET;
316 } else {
317 result.quality = MatchQuality::MULTIPLE_ZONES_DIFFERENT_OFFSET;
318 }
319 return result;
320 }
321
GetTZLookupDataPath()322 const char *ZoneUtil::GetTZLookupDataPath()
323 {
324 using std::filesystem::directory_iterator;
325 struct stat s;
326 if (stat(DISTRO_COUNTRY_ZONE_DATA_PATH, &s) == 0) {
327 return DISTRO_COUNTRY_ZONE_DATA_PATH;
328 }
329 if (stat(COUNTRY_ZONE_DATA_PATH, &s) == 0) {
330 return COUNTRY_ZONE_DATA_PATH;
331 } else {
332 return nullptr;
333 }
334 }
335
IsFindCountry(xmlDocPtr & doc,xmlNodePtr & cur,xmlNodePtr & value,std::string & region)336 bool ZoneUtil::IsFindCountry(xmlDocPtr &doc, xmlNodePtr &cur, xmlNodePtr &value,
337 std::string ®ion)
338 {
339 while (cur != nullptr && xmlStrcmp(cur->name, reinterpret_cast<const xmlChar *>(SECOND_TAG)) == 0) {
340 value = cur->xmlChildrenNode;
341 if (value == nullptr) {
342 cur = cur->next;
343 continue;
344 }
345 if (xmlStrcmp(value->name, reinterpret_cast<const xmlChar*>(CODE_TAG)) != 0) {
346 xmlFreeDoc(doc);
347 HILOG_ERROR_I18N("ZoneUtil::GetCountryZones invalid code_tag");
348 return false;
349 }
350 xmlChar *codePtr = xmlNodeGetContent(value);
351 if (codePtr == nullptr) {
352 cur = cur->next;
353 continue;
354 }
355 const char* xmlRegion = reinterpret_cast<const char*>(codePtr);
356 if (region.compare(xmlRegion) == 0) {
357 xmlFree(codePtr);
358 return true;
359 } else {
360 xmlFree(codePtr);
361 cur = cur->next;
362 continue;
363 }
364 }
365 return false;
366 }
367
GetCountryZones(std::string & region,std::string & defaultTimzone,bool & isBoosted,std::vector<std::string> & zones)368 void ZoneUtil::GetCountryZones(std::string ®ion, std::string &defaultTimzone, bool &isBoosted,
369 std::vector<std::string> &zones)
370 {
371 xmlKeepBlanksDefault(0);
372 xmlDocPtr doc = xmlParseFile(TZLOOKUP_FILE_PATH);
373 if (!doc) {
374 HILOG_ERROR_I18N("ZoneUtil::GetCountryZones can not open tzlookup.xml");
375 return;
376 }
377 xmlNodePtr cur = xmlDocGetRootElement(doc);
378 if (!cur || xmlStrcmp(cur->name, reinterpret_cast<const xmlChar *>(ROOT_TAG)) != 0) {
379 xmlFreeDoc(doc);
380 HILOG_ERROR_I18N("ZoneUtil::GetCountryZones invalid Root_tag");
381 return;
382 }
383 cur = cur->xmlChildrenNode;
384 xmlNodePtr value;
385 bool findCountry = IsFindCountry(doc, cur, value, region);
386 if (findCountry) {
387 value = value->next;
388 GetDefaultAndBoost(value, defaultTimzone, isBoosted, zones);
389 }
390 xmlUnlinkNode(cur);
391 xmlFreeNode(cur);
392 xmlFreeDoc(doc);
393 return;
394 }
395
GetDefaultAndBoost(xmlNodePtr & value,std::string & defaultTimezone,bool & isBoosted,std::vector<std::string> & zones)396 void ZoneUtil::GetDefaultAndBoost(xmlNodePtr &value, std::string &defaultTimezone, bool &isBoosted,
397 std::vector<std::string> &zones)
398 {
399 if (value == nullptr || xmlStrcmp(value->name, reinterpret_cast<const xmlChar*>(DEFAULT_TAG)) != 0) {
400 HILOG_ERROR_I18N("ZoneUtil::GetDefaultAndBoost invalid default_tag");
401 return;
402 }
403 xmlChar *defaultPtr = xmlNodeGetContent(value);
404 if (defaultPtr != nullptr) {
405 defaultTimezone = reinterpret_cast<const char*>(defaultPtr);
406 xmlFree(defaultPtr);
407 }
408 value = value->next;
409 if (value == nullptr) {
410 HILOG_ERROR_I18N("ZoneUtil::GetDefaultAndBoost doesn't contains id");
411 return;
412 }
413 if (xmlStrcmp(value->name, reinterpret_cast<const xmlChar *>(BOOSTED_TAG)) == 0) {
414 isBoosted = true;
415 value = value->next;
416 } else {
417 isBoosted = false;
418 }
419 GetTimezones(value, zones);
420 }
421
GetTimezones(xmlNodePtr & value,std::vector<std::string> & zones)422 void ZoneUtil::GetTimezones(xmlNodePtr &value, std::vector<std::string> &zones)
423 {
424 if (xmlStrcmp(value->name, reinterpret_cast<const xmlChar *>(TIMEZONES_TAG)) != 0) {
425 HILOG_ERROR_I18N("ZoneUtil::GetTimezones invalid timezones_tag");
426 return;
427 }
428 value = value->xmlChildrenNode;
429 while (value != nullptr) {
430 if (xmlStrcmp(value->name, reinterpret_cast<const xmlChar *>(ID_TAG)) != 0) {
431 HILOG_ERROR_I18N("ZoneUtil::GetTimezones invalid id_tag");
432 return;
433 }
434 xmlChar *idPtr = xmlNodeGetContent(value);
435 if (idPtr != nullptr) {
436 zones.push_back(reinterpret_cast<const char*>(idPtr));
437 xmlFree(idPtr);
438 }
439 value = value->next;
440 }
441 }
442
GetICUCountryZones(std::string & region,std::vector<std::string> & zones,std::string & defaultTimezone)443 void ZoneUtil::GetICUCountryZones(std::string ®ion, std::vector<std::string> &zones, std::string &defaultTimezone)
444 {
445 I18nErrorCode errorCode = I18nErrorCode::SUCCESS;
446 std::set<std::string> validZoneIds = I18nTimeZone::GetAvailableIDs(errorCode);
447 if (errorCode != I18nErrorCode::SUCCESS) {
448 HILOG_ERROR_I18N("ZoneUtil::GetICUCountryZones can not get icu data");
449 }
450 std::set<std::string> countryZoneIds;
451 StringEnumeration *strEnum = TimeZone::createEnumeration(region.c_str());
452 UErrorCode status = U_ZERO_ERROR;
453 const UnicodeString *timezoneIdUStr = strEnum == nullptr ? nullptr : strEnum->snext(status);
454 while (timezoneIdUStr != nullptr && U_SUCCESS(status)) {
455 UnicodeString canonicalUnistring;
456 TimeZone::getCanonicalID(*timezoneIdUStr, canonicalUnistring, status);
457 std::string timezoneId;
458 canonicalUnistring.toUTF8String(timezoneId);
459 if (validZoneIds.find(timezoneId) != validZoneIds.end()) {
460 countryZoneIds.insert(timezoneId);
461 }
462 timezoneIdUStr = strEnum->snext(status);
463 }
464 for (auto it = countryZoneIds.begin(); it != countryZoneIds.end(); ++it) {
465 zones.push_back(*it);
466 }
467 if (defaultMap.find(region) != defaultMap.end()) {
468 defaultTimezone = defaultMap[region];
469 } else {
470 if (zones.size() > 0) {
471 defaultTimezone = zones[0];
472 }
473 }
474 }
475
Match(std::vector<std::string> & zones,NITZData & nitzData,std::string & systemTimezone)476 CountryResult ZoneUtil::Match(std::vector<std::string> &zones, NITZData &nitzData, std::string &systemTimezone)
477 {
478 bool isOnlyMatch = true;
479 std::string matchedZoneId;
480 bool local = false;
481 bool useSystemTimezone = false;
482 for (size_t i = 0; i < zones.size(); i++) {
483 std::string zoneId = zones[i];
484 UnicodeString unicodeZoneID(zoneId.data(), zoneId.length());
485 TimeZone *timezone = TimeZone::createTimeZone(unicodeZoneID);
486 int32_t rawOffset;
487 int32_t dstOffset;
488 UErrorCode status = UErrorCode::U_ZERO_ERROR;
489 timezone->getOffset(nitzData.currentMillis, static_cast<UBool>(local), rawOffset, dstOffset, status);
490 if ((nitzData.totalOffset - rawOffset == dstOffset) &&
491 (nitzData.isDST < 0 || nitzData.isDST == (dstOffset != 0))) {
492 if (matchedZoneId.length() == 0) {
493 matchedZoneId = zoneId;
494 } else {
495 isOnlyMatch = false;
496 }
497 if (strcmp(zoneId.c_str(), systemTimezone.c_str()) == 0) {
498 matchedZoneId = systemTimezone;
499 useSystemTimezone = true;
500 }
501 if (!isOnlyMatch && useSystemTimezone) {
502 break;
503 }
504 }
505 }
506 CountryResult result = {isOnlyMatch, MatchQuality::DEFAULT_BOOSTED, matchedZoneId};
507 return result;
508 }
509
CheckSameDstOffset(std::vector<std::string> & zones,std::string & defaultTimezoneId,int64_t currentMillis)510 bool ZoneUtil::CheckSameDstOffset(std::vector<std::string> &zones, std::string &defaultTimezoneId,
511 int64_t currentMillis)
512 {
513 UnicodeString defaultID(defaultTimezoneId.data(), defaultTimezoneId.length());
514 TimeZone *defaultTimezone = TimeZone::createTimeZone(defaultID);
515 int32_t rawOffset = 0;
516 int32_t dstOffset = 0;
517 bool local = false;
518 UErrorCode status = U_ZERO_ERROR;
519 defaultTimezone->getOffset(currentMillis, (UBool)local, rawOffset, dstOffset, status);
520 if (U_FAILURE(status)) {
521 HILOG_ERROR_I18N("ZoneUtil::CheckSameDstOffset can not get timezone defaultID offset");
522 return false;
523 }
524 int32_t totalOffset = rawOffset + dstOffset;
525 for (size_t i = 0; i < zones.size(); i++) {
526 UnicodeString unicodeZoneID(zones[i].data(), zones[i].length());
527 TimeZone *timezone = TimeZone::createTimeZone(unicodeZoneID);
528 timezone->getOffset(currentMillis, (UBool)local, rawOffset, dstOffset, status);
529 if (U_FAILURE(status)) {
530 HILOG_ERROR_I18N("ZoneUtil::CheckSameDstOffset can not get timezone unicodeZoneID offset");
531 return false;
532 }
533 if (totalOffset - rawOffset != dstOffset) {
534 return false;
535 }
536 }
537 return true;
538 }
539