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