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 string *region_code = new(nothrow) string();
100 if (!region_code) {
101 return "";
102 }
103 phone_util.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 string *region_code = new(nothrow) string();
132 if (!region_code) {
133 return "";
134 }
135 phone_util.GetRegionCodeForCountryCode(number, region_code);
136 if (!region_code) {
137 return "";
138 }
139 string ret = GetDefaultZone(*region_code, offset);
140 delete region_code;
141 return ret;
142 }
143
GetZoneList(const string country,vector<string> & retVec)144 void ZoneUtil::GetZoneList(const string country, vector<string> &retVec)
145 {
146 StringEnumeration *strEnum = TimeZone::createEnumeration(country.c_str());
147 GetList(strEnum, retVec);
148 if (strEnum != nullptr) {
149 delete strEnum;
150 strEnum = nullptr;
151 }
152 }
153
GetZoneList(const string country,const int32_t offset,vector<string> & retVec)154 void ZoneUtil::GetZoneList(const string country, const int32_t offset, vector<string> &retVec)
155 {
156 UErrorCode status = U_ZERO_ERROR;
157 StringEnumeration *strEnum =
158 TimeZone::createTimeZoneIDEnumeration(UCAL_ZONE_TYPE_ANY, country.c_str(), &offset, status);
159 if (status != U_ZERO_ERROR) {
160 delete strEnum;
161 strEnum = nullptr;
162 return;
163 }
164 GetList(strEnum, retVec);
165 if (strEnum != nullptr) {
166 delete strEnum;
167 strEnum = nullptr;
168 }
169 }
170
GetString(StringEnumeration * strEnum,string & ret)171 void ZoneUtil::GetString(StringEnumeration *strEnum, string& ret)
172 {
173 UErrorCode status = U_ZERO_ERROR;
174 UnicodeString uniString;
175 if (!strEnum) {
176 return;
177 }
178 int32_t count = strEnum->count(status);
179 if ((status != U_ZERO_ERROR) || count <= 0) {
180 return;
181 }
182 const UnicodeString *uniStr = strEnum->snext(status);
183 if ((status != U_ZERO_ERROR) || (!uniStr)) {
184 return;
185 }
186 UnicodeString canonicalUnistring;
187 TimeZone::getCanonicalID(*uniStr, canonicalUnistring, status);
188 if (status != U_ZERO_ERROR) {
189 return;
190 }
191 canonicalUnistring.toUTF8String(ret);
192 return;
193 }
194
GetList(StringEnumeration * strEnum,vector<string> & retVec)195 void ZoneUtil::GetList(StringEnumeration *strEnum, vector<string> &retVec)
196 {
197 if (!strEnum) {
198 return;
199 }
200 UErrorCode status = U_ZERO_ERROR;
201 int32_t count = strEnum->count(status);
202 if (count <= 0 || status != U_ZERO_ERROR) {
203 return;
204 }
205 while (count > 0) {
206 const UnicodeString *uniStr = strEnum->snext(status);
207 if ((!uniStr) || (status != U_ZERO_ERROR)) {
208 retVec.clear();
209 break;
210 }
211 UnicodeString canonicalUnistring;
212 TimeZone::getCanonicalID(*uniStr, canonicalUnistring, status);
213 if (status != U_ZERO_ERROR) {
214 retVec.clear();
215 break;
216 }
217 string canonicalString = "";
218 canonicalUnistring.toUTF8String(canonicalString);
219 if ((canonicalString != "") && (find(retVec.begin(), retVec.end(), canonicalString) == retVec.end())) {
220 retVec.push_back(canonicalString);
221 }
222 --count;
223 }
224 return;
225 }
226
Init()227 bool ZoneUtil::Init()
228 {
229 SetHwIcuDirectory();
230 return true;
231 }
232
LookupTimezoneByCountryAndNITZ(std::string & region,NITZData & nitzData)233 CountryResult ZoneUtil::LookupTimezoneByCountryAndNITZ(std::string ®ion, NITZData &nitzData)
234 {
235 std::vector<std::string> zones;
236 bool isBoosted = false;
237 std::string defaultTimezone;
238 std::string systemTimezone = ReadSystemParameter(TIMEZONE_KEY, SYS_PARAM_LEN);
239 if (systemTimezone.length() == 0) {
240 systemTimezone = DEFAULT_TIMEZONE;
241 }
242 if (CheckFileExist()) {
243 HiLog::Info(LABEL, "ZoneUtil::LookupTimezoneByCountryAndNITZ use tzlookup.xml");
244 GetCountryZones(region, defaultTimezone, isBoosted, zones);
245 } else {
246 HiLog::Info(LABEL, "ZoneUtil::LookupTimezoneByCountryAndNITZ use icu data");
247 GetICUCountryZones(region, zones, defaultTimezone);
248 }
249 return Match(zones, nitzData, systemTimezone);
250 }
251
LookupTimezoneByNITZ(NITZData & nitzData)252 CountryResult ZoneUtil::LookupTimezoneByNITZ(NITZData &nitzData)
253 {
254 std::string systemTimezone = ReadSystemParameter(TIMEZONE_KEY, SYS_PARAM_LEN);
255 if (systemTimezone.length() == 0) {
256 systemTimezone = DEFAULT_TIMEZONE;
257 }
258 I18nErrorCode status = I18nErrorCode::SUCCESS;
259 std::set<std::string> icuTimezones = I18nTimeZone::GetAvailableIDs(status);
260 if (status != I18nErrorCode::SUCCESS) {
261 HiLog::Error(LABEL, "ZoneUtil::LookupTimezoneByNITZ can not get icu data");
262 }
263 std::vector<std::string> validZones;
264 for (auto it = icuTimezones.begin(); it != icuTimezones.end(); ++it) {
265 validZones.push_back(*it);
266 }
267
268 CountryResult result = Match(validZones, nitzData, systemTimezone);
269 if (result.timezoneId.length() == 0 && nitzData.isDST >= 0) {
270 NITZData newNITZData = { -1, nitzData.totalOffset, nitzData.currentMillis }; // -1 means not consider DST
271 result = Match(validZones, newNITZData, systemTimezone);
272 }
273 return result;
274 }
275
LookupTimezoneByCountry(std::string & region,int64_t currentMillis)276 CountryResult ZoneUtil::LookupTimezoneByCountry(std::string ®ion, int64_t currentMillis)
277 {
278 std::vector<std::string> zones;
279 bool isBoosted = false;
280 std::string defaultTimezone;
281 CountryResult result = { true, MatchQuality::DEFAULT_BOOSTED, defaultTimezone };
282 if (CheckFileExist()) {
283 HiLog::Info(LABEL, "ZoneUtil::LookupTimezoneByCountry use tzlookup.xml");
284 GetCountryZones(region, defaultTimezone, isBoosted, zones);
285 if (defaultTimezone.length() == 0) {
286 HiLog::Error(LABEL, "ZoneUtil::LookupTimezoneByCountry can't find default timezone for region %{public}s",
287 region.c_str());
288 }
289 } else {
290 HiLog::Info(LABEL, "ZoneUtil::LookupTimezoneByCountry use icu data");
291 GetICUCountryZones(region, zones, defaultTimezone);
292 }
293 result.timezoneId = defaultTimezone;
294 if (isBoosted) {
295 return result;
296 }
297 if (zones.size() == 0) {
298 result.quality = MatchQuality::MULTIPLE_ZONES_DIFFERENT_OFFSET;
299 } else if (zones.size() == 1) {
300 result.quality = MatchQuality::SINGLE_ZONE;
301 } else if (CheckSameDstOffset(zones, defaultTimezone, currentMillis)) {
302 result.quality = MatchQuality::MULTIPLE_ZONES_SAME_OFFSET;
303 } else {
304 result.quality = MatchQuality::MULTIPLE_ZONES_DIFFERENT_OFFSET;
305 }
306 return result;
307 }
308
CheckFileExist()309 bool ZoneUtil::CheckFileExist()
310 {
311 using std::filesystem::directory_iterator;
312 struct stat s;
313 if (stat(COUNTRY_ZONE_DATA_PATH, &s) == 0) {
314 return true;
315 } else {
316 return false;
317 }
318 }
319
GetCountryZones(std::string & region,std::string & defaultTimzone,bool & isBoosted,std::vector<std::string> & zones)320 void ZoneUtil::GetCountryZones(std::string ®ion, std::string &defaultTimzone, bool &isBoosted,
321 std::vector<std::string> &zones)
322 {
323 xmlKeepBlanksDefault(0);
324 xmlDocPtr doc = xmlParseFile(COUNTRY_ZONE_DATA_PATH);
325 if (!doc) {
326 HiLog::Error(LABEL, "ZoneUtil::GetCountryZones can not open tzlookup.xml");
327 return;
328 }
329 xmlNodePtr cur = xmlDocGetRootElement(doc);
330 if (!cur || xmlStrcmp(cur->name, reinterpret_cast<const xmlChar *>(ROOT_TAG)) != 0) {
331 xmlFreeDoc(doc);
332 HiLog::Error(LABEL, "ZoneUtil::GetCountryZones invalid Root_tag");
333 return;
334 }
335 cur = cur->xmlChildrenNode;
336 xmlNodePtr value;
337 bool findCountry = false;
338 while (cur != nullptr && xmlStrcmp(cur->name, reinterpret_cast<const xmlChar *>(SECOND_TAG)) == 0) {
339 value = cur->xmlChildrenNode;
340 if (xmlStrcmp(value->name, reinterpret_cast<const xmlChar*>(CODE_TAG)) != 0) {
341 HiLog::Error(LABEL, "ZoneUtil::GetCountryZones invalid code_tag");
342 return;
343 }
344 xmlChar *codePtr = xmlNodeGetContent(value);
345 if (strcmp(region.c_str(), reinterpret_cast<const char*>(codePtr)) == 0) {
346 findCountry = true;
347 xmlFree(codePtr);
348 break;
349 } else {
350 xmlFree(codePtr);
351 cur = cur->next;
352 continue;
353 }
354 }
355 if (findCountry) {
356 value = value->next;
357 GetDefaultAndBoost(value, defaultTimzone, isBoosted, zones);
358 }
359 xmlFreeDoc(doc);
360 return;
361 }
362
GetDefaultAndBoost(xmlNodePtr & value,std::string & defaultTimezone,bool & isBoosted,std::vector<std::string> & zones)363 void ZoneUtil::GetDefaultAndBoost(xmlNodePtr &value, std::string &defaultTimezone, bool &isBoosted,
364 std::vector<std::string> &zones)
365 {
366 if (value == nullptr || xmlStrcmp(value->name, reinterpret_cast<const xmlChar*>(DEFAULT_TAG)) != 0) {
367 HiLog::Error(LABEL, "ZoneUtil::GetDefaultAndBoost invalid default_tag");
368 return;
369 }
370 xmlChar *defaultPtr = xmlNodeGetContent(value);
371 defaultTimezone = reinterpret_cast<const char*>(defaultPtr);
372 xmlFree(defaultPtr);
373 value = value->next;
374 if (value == nullptr) {
375 HiLog::Error(LABEL, "ZoneUtil::GetDefaultAndBoost doesn't contains id");
376 return;
377 }
378 if (xmlStrcmp(value->name, reinterpret_cast<const xmlChar *>(BOOSTED_TAG)) == 0) {
379 isBoosted = true;
380 value = value->next;
381 } else {
382 isBoosted = false;
383 }
384 GetTimezones(value, zones);
385 }
386
GetTimezones(xmlNodePtr & value,std::vector<std::string> & zones)387 void ZoneUtil::GetTimezones(xmlNodePtr &value, std::vector<std::string> &zones)
388 {
389 if (xmlStrcmp(value->name, reinterpret_cast<const xmlChar *>(TIMEZONES_TAG)) != 0) {
390 HiLog::Error(LABEL, "ZoneUtil::GetTimezones invalid timezones_tag");
391 return;
392 }
393 value = value->xmlChildrenNode;
394 while (value != nullptr) {
395 if (xmlStrcmp(value->name, reinterpret_cast<const xmlChar *>(ID_TAG)) != 0) {
396 HiLog::Error(LABEL, "ZoneUtil::GetTimezones invalid id_tag");
397 return;
398 }
399 xmlChar *idPtr = xmlNodeGetContent(value);
400 zones.push_back(reinterpret_cast<const char*>(idPtr));
401 xmlFree(idPtr);
402 value = value->next;
403 }
404 }
405
GetICUCountryZones(std::string & region,std::vector<std::string> & zones,std::string & defaultTimezone)406 void ZoneUtil::GetICUCountryZones(std::string ®ion, std::vector<std::string> &zones, std::string &defaultTimezone)
407 {
408 I18nErrorCode errorCode = I18nErrorCode::SUCCESS;
409 std::set<std::string> validZoneIds = I18nTimeZone::GetAvailableIDs(errorCode);
410 if (errorCode != I18nErrorCode::SUCCESS) {
411 HiLog::Error(LABEL, "ZoneUtil::LookupTimezoneByNITZ can not get icu data");
412 }
413 std::set<std::string> countryZoneIds;
414 StringEnumeration *strEnum = TimeZone::createEnumeration(region.c_str());
415 UErrorCode status = U_ZERO_ERROR;
416 const UnicodeString *timezoneIdUStr = strEnum->snext(status);
417 while (timezoneIdUStr != nullptr && U_SUCCESS(status)) {
418 UnicodeString canonicalUnistring;
419 TimeZone::getCanonicalID(*timezoneIdUStr, canonicalUnistring, status);
420 std::string timezoneId;
421 canonicalUnistring.toUTF8String(timezoneId);
422 if (validZoneIds.find(timezoneId) != validZoneIds.end()) {
423 countryZoneIds.insert(timezoneId);
424 }
425 timezoneIdUStr = strEnum->snext(status);
426 }
427 for (auto it = countryZoneIds.begin(); it != countryZoneIds.end(); ++it) {
428 zones.push_back(*it);
429 }
430 if (defaultMap.find(region) != defaultMap.end()) {
431 defaultTimezone = defaultMap[region];
432 } else {
433 if (zones.size() > 0) {
434 defaultTimezone = zones[0];
435 }
436 }
437 }
438
Match(std::vector<std::string> & zones,NITZData & nitzData,std::string & systemTimezone)439 CountryResult ZoneUtil::Match(std::vector<std::string> &zones, NITZData &nitzData, std::string &systemTimezone)
440 {
441 bool isOnlyMatch = true;
442 std::string matchedZoneId;
443 bool local = false;
444 bool useSystemTimezone = false;
445 for (size_t i = 0; i < zones.size(); i++) {
446 std::string zoneId = zones[i];
447 UnicodeString unicodeZoneID(zoneId.data(), zoneId.length());
448 TimeZone *timezone = TimeZone::createTimeZone(unicodeZoneID);
449 int32_t rawOffset;
450 int32_t dstOffset;
451 UErrorCode status = UErrorCode::U_ZERO_ERROR;
452 timezone->getOffset(nitzData.currentMillis, static_cast<UBool>(local), rawOffset, dstOffset, status);
453 if ((nitzData.totalOffset - rawOffset == dstOffset) &&
454 (nitzData.isDST < 0 || nitzData.isDST == (dstOffset != 0))) {
455 if (matchedZoneId.length() == 0) {
456 matchedZoneId = zoneId;
457 } else {
458 isOnlyMatch = false;
459 }
460 if (strcmp(zoneId.c_str(), systemTimezone.c_str()) == 0) {
461 matchedZoneId = systemTimezone;
462 useSystemTimezone = true;
463 }
464 if (!isOnlyMatch && useSystemTimezone) {
465 break;
466 }
467 }
468 }
469 CountryResult result = {isOnlyMatch, MatchQuality::DEFAULT_BOOSTED, matchedZoneId};
470 return result;
471 }
472
CheckSameDstOffset(std::vector<std::string> & zones,std::string & defaultTimezoneId,int64_t currentMillis)473 bool ZoneUtil::CheckSameDstOffset(std::vector<std::string> &zones, std::string &defaultTimezoneId,
474 int64_t currentMillis)
475 {
476 UnicodeString defaultID(defaultTimezoneId.data(), defaultTimezoneId.length());
477 TimeZone *defaultTimezone = TimeZone::createTimeZone(defaultID);
478 int32_t rawOffset = 0;
479 int32_t dstOffset = 0;
480 bool local = false;
481 UErrorCode status = U_ZERO_ERROR;
482 defaultTimezone->getOffset(currentMillis, (UBool)local, rawOffset, dstOffset, status);
483 if (U_FAILURE(status)) {
484 HiLog::Error(LABEL, "ZoneUtil::CheckSameDstOffset can not get timezone offset");
485 return false;
486 }
487 int32_t totalOffset = rawOffset + dstOffset;
488 for (size_t i = 0; i < zones.size(); i++) {
489 UnicodeString unicodeZoneID(zones[i].data(), zones[i].length());
490 TimeZone *timezone = TimeZone::createTimeZone(unicodeZoneID);
491 timezone->getOffset(currentMillis, (UBool)local, rawOffset, dstOffset, status);
492 if (U_FAILURE(status)) {
493 HiLog::Error(LABEL, "ZoneUtil::CheckSameDstOffset can not get timezone offset");
494 return false;
495 }
496 if (totalOffset - rawOffset != dstOffset) {
497 return false;
498 }
499 }
500 return true;
501 }
502