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 (status != U_ZERO_ERROR) {
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 (status != U_ZERO_ERROR) {
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 ((status != U_ZERO_ERROR) || count <= 0) {
193 return;
194 }
195 const UnicodeString *uniStr = strEnum->snext(status);
196 if ((status != U_ZERO_ERROR) || (!uniStr)) {
197 return;
198 }
199 UnicodeString canonicalUnistring;
200 TimeZone::getCanonicalID(*uniStr, canonicalUnistring, status);
201 if (status != U_ZERO_ERROR) {
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 || status != U_ZERO_ERROR) {
216 return;
217 }
218 while (count > 0) {
219 const UnicodeString *uniStr = strEnum->snext(status);
220 if ((!uniStr) || (status != U_ZERO_ERROR)) {
221 retVec.clear();
222 break;
223 }
224 UnicodeString canonicalUnistring;
225 TimeZone::getCanonicalID(*uniStr, canonicalUnistring, status);
226 if (status != U_ZERO_ERROR) {
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
GetCountryZones(std::string & region,std::string & defaultTimzone,bool & isBoosted,std::vector<std::string> & zones)336 void ZoneUtil::GetCountryZones(std::string ®ion, std::string &defaultTimzone, bool &isBoosted,
337 std::vector<std::string> &zones)
338 {
339 xmlKeepBlanksDefault(0);
340 xmlDocPtr doc = xmlParseFile(TZLOOKUP_FILE_PATH);
341 if (!doc) {
342 HILOG_ERROR_I18N("ZoneUtil::GetCountryZones can not open tzlookup.xml");
343 return;
344 }
345 xmlNodePtr cur = xmlDocGetRootElement(doc);
346 if (!cur || xmlStrcmp(cur->name, reinterpret_cast<const xmlChar *>(ROOT_TAG)) != 0) {
347 xmlFreeDoc(doc);
348 HILOG_ERROR_I18N("ZoneUtil::GetCountryZones invalid Root_tag");
349 return;
350 }
351 cur = cur->xmlChildrenNode;
352 xmlNodePtr value;
353 bool findCountry = false;
354 while (cur != nullptr && xmlStrcmp(cur->name, reinterpret_cast<const xmlChar *>(SECOND_TAG)) == 0) {
355 value = cur->xmlChildrenNode;
356 if (xmlStrcmp(value->name, reinterpret_cast<const xmlChar*>(CODE_TAG)) != 0) {
357 xmlFreeDoc(doc);
358 HILOG_ERROR_I18N("ZoneUtil::GetCountryZones invalid code_tag");
359 return;
360 }
361 xmlChar *codePtr = xmlNodeGetContent(value);
362 if (codePtr == nullptr) {
363 cur = cur->next;
364 continue;
365 }
366 if (strcmp(region.c_str(), reinterpret_cast<const char*>(codePtr)) == 0) {
367 findCountry = true;
368 xmlFree(codePtr);
369 break;
370 } else {
371 xmlFree(codePtr);
372 cur = cur->next;
373 continue;
374 }
375 }
376 if (findCountry) {
377 value = value->next;
378 GetDefaultAndBoost(value, defaultTimzone, isBoosted, zones);
379 }
380 xmlFreeDoc(doc);
381 return;
382 }
383
GetDefaultAndBoost(xmlNodePtr & value,std::string & defaultTimezone,bool & isBoosted,std::vector<std::string> & zones)384 void ZoneUtil::GetDefaultAndBoost(xmlNodePtr &value, std::string &defaultTimezone, bool &isBoosted,
385 std::vector<std::string> &zones)
386 {
387 if (value == nullptr || xmlStrcmp(value->name, reinterpret_cast<const xmlChar*>(DEFAULT_TAG)) != 0) {
388 HILOG_ERROR_I18N("ZoneUtil::GetDefaultAndBoost invalid default_tag");
389 return;
390 }
391 xmlChar *defaultPtr = xmlNodeGetContent(value);
392 if (defaultPtr != nullptr) {
393 defaultTimezone = reinterpret_cast<const char*>(defaultPtr);
394 xmlFree(defaultPtr);
395 }
396 value = value->next;
397 if (value == nullptr) {
398 HILOG_ERROR_I18N("ZoneUtil::GetDefaultAndBoost doesn't contains id");
399 return;
400 }
401 if (xmlStrcmp(value->name, reinterpret_cast<const xmlChar *>(BOOSTED_TAG)) == 0) {
402 isBoosted = true;
403 value = value->next;
404 } else {
405 isBoosted = false;
406 }
407 GetTimezones(value, zones);
408 }
409
GetTimezones(xmlNodePtr & value,std::vector<std::string> & zones)410 void ZoneUtil::GetTimezones(xmlNodePtr &value, std::vector<std::string> &zones)
411 {
412 if (xmlStrcmp(value->name, reinterpret_cast<const xmlChar *>(TIMEZONES_TAG)) != 0) {
413 HILOG_ERROR_I18N("ZoneUtil::GetTimezones invalid timezones_tag");
414 return;
415 }
416 value = value->xmlChildrenNode;
417 while (value != nullptr) {
418 if (xmlStrcmp(value->name, reinterpret_cast<const xmlChar *>(ID_TAG)) != 0) {
419 HILOG_ERROR_I18N("ZoneUtil::GetTimezones invalid id_tag");
420 return;
421 }
422 xmlChar *idPtr = xmlNodeGetContent(value);
423 if (idPtr != nullptr) {
424 zones.push_back(reinterpret_cast<const char*>(idPtr));
425 xmlFree(idPtr);
426 }
427 value = value->next;
428 }
429 }
430
GetICUCountryZones(std::string & region,std::vector<std::string> & zones,std::string & defaultTimezone)431 void ZoneUtil::GetICUCountryZones(std::string ®ion, std::vector<std::string> &zones, std::string &defaultTimezone)
432 {
433 I18nErrorCode errorCode = I18nErrorCode::SUCCESS;
434 std::set<std::string> validZoneIds = I18nTimeZone::GetAvailableIDs(errorCode);
435 if (errorCode != I18nErrorCode::SUCCESS) {
436 HILOG_ERROR_I18N("ZoneUtil::GetICUCountryZones can not get icu data");
437 }
438 std::set<std::string> countryZoneIds;
439 StringEnumeration *strEnum = TimeZone::createEnumeration(region.c_str());
440 UErrorCode status = U_ZERO_ERROR;
441 const UnicodeString *timezoneIdUStr = strEnum->snext(status);
442 while (timezoneIdUStr != nullptr && U_SUCCESS(status)) {
443 UnicodeString canonicalUnistring;
444 TimeZone::getCanonicalID(*timezoneIdUStr, canonicalUnistring, status);
445 std::string timezoneId;
446 canonicalUnistring.toUTF8String(timezoneId);
447 if (validZoneIds.find(timezoneId) != validZoneIds.end()) {
448 countryZoneIds.insert(timezoneId);
449 }
450 timezoneIdUStr = strEnum->snext(status);
451 }
452 for (auto it = countryZoneIds.begin(); it != countryZoneIds.end(); ++it) {
453 zones.push_back(*it);
454 }
455 if (defaultMap.find(region) != defaultMap.end()) {
456 defaultTimezone = defaultMap[region];
457 } else {
458 if (zones.size() > 0) {
459 defaultTimezone = zones[0];
460 }
461 }
462 }
463
Match(std::vector<std::string> & zones,NITZData & nitzData,std::string & systemTimezone)464 CountryResult ZoneUtil::Match(std::vector<std::string> &zones, NITZData &nitzData, std::string &systemTimezone)
465 {
466 bool isOnlyMatch = true;
467 std::string matchedZoneId;
468 bool local = false;
469 bool useSystemTimezone = false;
470 for (size_t i = 0; i < zones.size(); i++) {
471 std::string zoneId = zones[i];
472 UnicodeString unicodeZoneID(zoneId.data(), zoneId.length());
473 TimeZone *timezone = TimeZone::createTimeZone(unicodeZoneID);
474 int32_t rawOffset;
475 int32_t dstOffset;
476 UErrorCode status = UErrorCode::U_ZERO_ERROR;
477 timezone->getOffset(nitzData.currentMillis, static_cast<UBool>(local), rawOffset, dstOffset, status);
478 if ((nitzData.totalOffset - rawOffset == dstOffset) &&
479 (nitzData.isDST < 0 || nitzData.isDST == (dstOffset != 0))) {
480 if (matchedZoneId.length() == 0) {
481 matchedZoneId = zoneId;
482 } else {
483 isOnlyMatch = false;
484 }
485 if (strcmp(zoneId.c_str(), systemTimezone.c_str()) == 0) {
486 matchedZoneId = systemTimezone;
487 useSystemTimezone = true;
488 }
489 if (!isOnlyMatch && useSystemTimezone) {
490 break;
491 }
492 }
493 }
494 CountryResult result = {isOnlyMatch, MatchQuality::DEFAULT_BOOSTED, matchedZoneId};
495 return result;
496 }
497
CheckSameDstOffset(std::vector<std::string> & zones,std::string & defaultTimezoneId,int64_t currentMillis)498 bool ZoneUtil::CheckSameDstOffset(std::vector<std::string> &zones, std::string &defaultTimezoneId,
499 int64_t currentMillis)
500 {
501 UnicodeString defaultID(defaultTimezoneId.data(), defaultTimezoneId.length());
502 TimeZone *defaultTimezone = TimeZone::createTimeZone(defaultID);
503 int32_t rawOffset = 0;
504 int32_t dstOffset = 0;
505 bool local = false;
506 UErrorCode status = U_ZERO_ERROR;
507 defaultTimezone->getOffset(currentMillis, (UBool)local, rawOffset, dstOffset, status);
508 if (U_FAILURE(status)) {
509 HILOG_ERROR_I18N("ZoneUtil::CheckSameDstOffset can not get timezone defaultID offset");
510 return false;
511 }
512 int32_t totalOffset = rawOffset + dstOffset;
513 for (size_t i = 0; i < zones.size(); i++) {
514 UnicodeString unicodeZoneID(zones[i].data(), zones[i].length());
515 TimeZone *timezone = TimeZone::createTimeZone(unicodeZoneID);
516 timezone->getOffset(currentMillis, (UBool)local, rawOffset, dstOffset, status);
517 if (U_FAILURE(status)) {
518 HILOG_ERROR_I18N("ZoneUtil::CheckSameDstOffset can not get timezone unicodeZoneID offset");
519 return false;
520 }
521 if (totalOffset - rawOffset != dstOffset) {
522 return false;
523 }
524 }
525 return true;
526 }
527