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 &region, 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 &region, 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 &region, 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 &region, 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