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 "hilog/log.h"
19 #include "i18n_timezone.h"
20 #include "ohos/init_data.h"
21 #include "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 static constexpr OHOS::HiviewDFX::HiLogLabel LABEL = { LOG_CORE, 0xD001E00, "ZoneUtil" };
31 using namespace OHOS::HiviewDFX;
32 const char *ZoneUtil::COUNTRY_ZONE_DATA_PATH = "/system/usr/ohos_timezone/tzlookup.xml";
33 const char *ZoneUtil::DEFAULT_TIMEZONE = "GMT";
34 const char *ZoneUtil::TIMEZONES_TAG = "timezones";
35 const char *ZoneUtil::ID_TAG = "id";
36 const char *ZoneUtil::DEFAULT_TAG = "default";
37 const char *ZoneUtil::BOOSTED_TAG = "defaultBoost";
38 const char *ZoneUtil::ROOT_TAG = "countryzones";
39 const char *ZoneUtil::SECOND_TAG = "country";
40 const char *ZoneUtil::CODE_TAG = "code";
41 const char *ZoneUtil::TIMEZONE_KEY = "persist.time.timezone";
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 (status != U_ZERO_ERROR) {
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 (status != U_ZERO_ERROR) {
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 ((status != U_ZERO_ERROR) || count <= 0) {
184 return;
185 }
186 const UnicodeString *uniStr = strEnum->snext(status);
187 if ((status != U_ZERO_ERROR) || (!uniStr)) {
188 return;
189 }
190 UnicodeString canonicalUnistring;
191 TimeZone::getCanonicalID(*uniStr, canonicalUnistring, status);
192 if (status != U_ZERO_ERROR) {
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 || status != U_ZERO_ERROR) {
207 return;
208 }
209 while (count > 0) {
210 const UnicodeString *uniStr = strEnum->snext(status);
211 if ((!uniStr) || (status != U_ZERO_ERROR)) {
212 retVec.clear();
213 break;
214 }
215 UnicodeString canonicalUnistring;
216 TimeZone::getCanonicalID(*uniStr, canonicalUnistring, status);
217 if (status != U_ZERO_ERROR) {
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 & region,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 (CheckFileExist()) {
246 bool isBoosted = false;
247 HiLog::Info(LABEL, "ZoneUtil::LookupTimezoneByCountryAndNITZ use tzlookup.xml");
248 GetCountryZones(region, defaultTimezone, isBoosted, zones);
249 } else {
250 HiLog::Info(LABEL, "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(LABEL, "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 & region,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 (CheckFileExist()) {
287 HiLog::Info(LABEL, "ZoneUtil::LookupTimezoneByCountry use tzlookup.xml");
288 GetCountryZones(region, defaultTimezone, isBoosted, zones);
289 if (defaultTimezone.length() == 0) {
290 HiLog::Error(LABEL, "ZoneUtil::LookupTimezoneByCountry can't find default timezone for region %{public}s",
291 region.c_str());
292 }
293 } else {
294 HiLog::Info(LABEL, "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
CheckFileExist()313 bool ZoneUtil::CheckFileExist()
314 {
315 using std::filesystem::directory_iterator;
316 struct stat s;
317 if (stat(COUNTRY_ZONE_DATA_PATH, &s) == 0) {
318 return true;
319 } else {
320 return false;
321 }
322 }
323
GetCountryZones(std::string & region,std::string & defaultTimzone,bool & isBoosted,std::vector<std::string> & zones)324 void ZoneUtil::GetCountryZones(std::string ®ion, std::string &defaultTimzone, bool &isBoosted,
325 std::vector<std::string> &zones)
326 {
327 xmlKeepBlanksDefault(0);
328 xmlDocPtr doc = xmlParseFile(COUNTRY_ZONE_DATA_PATH);
329 if (!doc) {
330 HiLog::Error(LABEL, "ZoneUtil::GetCountryZones can not open tzlookup.xml");
331 return;
332 }
333 xmlNodePtr cur = xmlDocGetRootElement(doc);
334 if (!cur || xmlStrcmp(cur->name, reinterpret_cast<const xmlChar *>(ROOT_TAG)) != 0) {
335 xmlFreeDoc(doc);
336 HiLog::Error(LABEL, "ZoneUtil::GetCountryZones invalid Root_tag");
337 return;
338 }
339 cur = cur->xmlChildrenNode;
340 xmlNodePtr value;
341 bool findCountry = false;
342 while (cur != nullptr && xmlStrcmp(cur->name, reinterpret_cast<const xmlChar *>(SECOND_TAG)) == 0) {
343 value = cur->xmlChildrenNode;
344 if (xmlStrcmp(value->name, reinterpret_cast<const xmlChar*>(CODE_TAG)) != 0) {
345 HiLog::Error(LABEL, "ZoneUtil::GetCountryZones invalid code_tag");
346 return;
347 }
348 xmlChar *codePtr = xmlNodeGetContent(value);
349 if (strcmp(region.c_str(), reinterpret_cast<const char*>(codePtr)) == 0) {
350 findCountry = true;
351 xmlFree(codePtr);
352 break;
353 } else {
354 xmlFree(codePtr);
355 cur = cur->next;
356 continue;
357 }
358 }
359 if (findCountry) {
360 value = value->next;
361 GetDefaultAndBoost(value, defaultTimzone, isBoosted, zones);
362 }
363 xmlFreeDoc(doc);
364 return;
365 }
366
GetDefaultAndBoost(xmlNodePtr & value,std::string & defaultTimezone,bool & isBoosted,std::vector<std::string> & zones)367 void ZoneUtil::GetDefaultAndBoost(xmlNodePtr &value, std::string &defaultTimezone, bool &isBoosted,
368 std::vector<std::string> &zones)
369 {
370 if (value == nullptr || xmlStrcmp(value->name, reinterpret_cast<const xmlChar*>(DEFAULT_TAG)) != 0) {
371 HiLog::Error(LABEL, "ZoneUtil::GetDefaultAndBoost invalid default_tag");
372 return;
373 }
374 xmlChar *defaultPtr = xmlNodeGetContent(value);
375 defaultTimezone = reinterpret_cast<const char*>(defaultPtr);
376 xmlFree(defaultPtr);
377 value = value->next;
378 if (value == nullptr) {
379 HiLog::Error(LABEL, "ZoneUtil::GetDefaultAndBoost doesn't contains id");
380 return;
381 }
382 if (xmlStrcmp(value->name, reinterpret_cast<const xmlChar *>(BOOSTED_TAG)) == 0) {
383 isBoosted = true;
384 value = value->next;
385 } else {
386 isBoosted = false;
387 }
388 GetTimezones(value, zones);
389 }
390
GetTimezones(xmlNodePtr & value,std::vector<std::string> & zones)391 void ZoneUtil::GetTimezones(xmlNodePtr &value, std::vector<std::string> &zones)
392 {
393 if (xmlStrcmp(value->name, reinterpret_cast<const xmlChar *>(TIMEZONES_TAG)) != 0) {
394 HiLog::Error(LABEL, "ZoneUtil::GetTimezones invalid timezones_tag");
395 return;
396 }
397 value = value->xmlChildrenNode;
398 while (value != nullptr) {
399 if (xmlStrcmp(value->name, reinterpret_cast<const xmlChar *>(ID_TAG)) != 0) {
400 HiLog::Error(LABEL, "ZoneUtil::GetTimezones invalid id_tag");
401 return;
402 }
403 xmlChar *idPtr = xmlNodeGetContent(value);
404 zones.push_back(reinterpret_cast<const char*>(idPtr));
405 xmlFree(idPtr);
406 value = value->next;
407 }
408 }
409
GetICUCountryZones(std::string & region,std::vector<std::string> & zones,std::string & defaultTimezone)410 void ZoneUtil::GetICUCountryZones(std::string ®ion, std::vector<std::string> &zones, std::string &defaultTimezone)
411 {
412 I18nErrorCode errorCode = I18nErrorCode::SUCCESS;
413 std::set<std::string> validZoneIds = I18nTimeZone::GetAvailableIDs(errorCode);
414 if (errorCode != I18nErrorCode::SUCCESS) {
415 HiLog::Error(LABEL, "ZoneUtil::LookupTimezoneByNITZ can not get icu data");
416 }
417 std::set<std::string> countryZoneIds;
418 StringEnumeration *strEnum = TimeZone::createEnumeration(region.c_str());
419 UErrorCode status = U_ZERO_ERROR;
420 const UnicodeString *timezoneIdUStr = strEnum->snext(status);
421 while (timezoneIdUStr != nullptr && U_SUCCESS(status)) {
422 UnicodeString canonicalUnistring;
423 TimeZone::getCanonicalID(*timezoneIdUStr, canonicalUnistring, status);
424 std::string timezoneId;
425 canonicalUnistring.toUTF8String(timezoneId);
426 if (validZoneIds.find(timezoneId) != validZoneIds.end()) {
427 countryZoneIds.insert(timezoneId);
428 }
429 timezoneIdUStr = strEnum->snext(status);
430 }
431 for (auto it = countryZoneIds.begin(); it != countryZoneIds.end(); ++it) {
432 zones.push_back(*it);
433 }
434 if (defaultMap.find(region) != defaultMap.end()) {
435 defaultTimezone = defaultMap[region];
436 } else {
437 if (zones.size() > 0) {
438 defaultTimezone = zones[0];
439 }
440 }
441 }
442
Match(std::vector<std::string> & zones,NITZData & nitzData,std::string & systemTimezone)443 CountryResult ZoneUtil::Match(std::vector<std::string> &zones, NITZData &nitzData, std::string &systemTimezone)
444 {
445 bool isOnlyMatch = true;
446 std::string matchedZoneId;
447 bool local = false;
448 bool useSystemTimezone = false;
449 for (size_t i = 0; i < zones.size(); i++) {
450 std::string zoneId = zones[i];
451 UnicodeString unicodeZoneID(zoneId.data(), zoneId.length());
452 TimeZone *timezone = TimeZone::createTimeZone(unicodeZoneID);
453 int32_t rawOffset;
454 int32_t dstOffset;
455 UErrorCode status = UErrorCode::U_ZERO_ERROR;
456 timezone->getOffset(nitzData.currentMillis, static_cast<UBool>(local), rawOffset, dstOffset, status);
457 if ((nitzData.totalOffset - rawOffset == dstOffset) &&
458 (nitzData.isDST < 0 || nitzData.isDST == (dstOffset != 0))) {
459 if (matchedZoneId.length() == 0) {
460 matchedZoneId = zoneId;
461 } else {
462 isOnlyMatch = false;
463 }
464 if (strcmp(zoneId.c_str(), systemTimezone.c_str()) == 0) {
465 matchedZoneId = systemTimezone;
466 useSystemTimezone = true;
467 }
468 if (!isOnlyMatch && useSystemTimezone) {
469 break;
470 }
471 }
472 }
473 CountryResult result = {isOnlyMatch, MatchQuality::DEFAULT_BOOSTED, matchedZoneId};
474 return result;
475 }
476
CheckSameDstOffset(std::vector<std::string> & zones,std::string & defaultTimezoneId,int64_t currentMillis)477 bool ZoneUtil::CheckSameDstOffset(std::vector<std::string> &zones, std::string &defaultTimezoneId,
478 int64_t currentMillis)
479 {
480 UnicodeString defaultID(defaultTimezoneId.data(), defaultTimezoneId.length());
481 TimeZone *defaultTimezone = TimeZone::createTimeZone(defaultID);
482 int32_t rawOffset = 0;
483 int32_t dstOffset = 0;
484 bool local = false;
485 UErrorCode status = U_ZERO_ERROR;
486 defaultTimezone->getOffset(currentMillis, (UBool)local, rawOffset, dstOffset, status);
487 if (U_FAILURE(status)) {
488 HiLog::Error(LABEL, "ZoneUtil::CheckSameDstOffset can not get timezone offset");
489 return false;
490 }
491 int32_t totalOffset = rawOffset + dstOffset;
492 for (size_t i = 0; i < zones.size(); i++) {
493 UnicodeString unicodeZoneID(zones[i].data(), zones[i].length());
494 TimeZone *timezone = TimeZone::createTimeZone(unicodeZoneID);
495 timezone->getOffset(currentMillis, (UBool)local, rawOffset, dstOffset, status);
496 if (U_FAILURE(status)) {
497 HiLog::Error(LABEL, "ZoneUtil::CheckSameDstOffset can not get timezone offset");
498 return false;
499 }
500 if (totalOffset - rawOffset != dstOffset) {
501 return false;
502 }
503 }
504 return true;
505 }
506