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