• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 &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 (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 &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 (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 &region, 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 &region, 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