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