• 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 "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::DEFAULT_TIMEZONE = "GMT";
32 const char *ZoneUtil::TIMEZONES_TAG = "timezones";
33 const char *ZoneUtil::ID_TAG = "id";
34 const char *ZoneUtil::DEFAULT_TAG = "default";
35 const char *ZoneUtil::BOOSTED_TAG = "defaultBoost";
36 const char *ZoneUtil::ROOT_TAG = "countryzones";
37 const char *ZoneUtil::SECOND_TAG = "country";
38 const char *ZoneUtil::CODE_TAG = "code";
39 const char *ZoneUtil::TIMEZONE_KEY = "persist.time.timezone";
40 
41 unordered_map<string, string> ZoneUtil::defaultMap = {
42     {"AQ", "Antarctica/McMurdo"},
43     {"AR", "America/Argentina/Buenos_Aires"},
44     {"AU", "Australia/Sydney"},
45     {"BR", "America/Noronha"},
46     {"CA", "America/St_Johns"},
47     {"CD", "Africa/Kinshasa"},
48     {"CL", "America/Santiago"},
49     {"CN", "Asia/Shanghai"},
50     {"CY", "Asia/Nicosia"},
51     {"DE", "Europe/Berlin"},
52     {"EC", "America/Guayaquil"},
53     {"ES", "Europe/Madrid"},
54     {"FM", "Pacific/Pohnpei"},
55     {"GL", "America/Godthab"},
56     {"ID", "Asia/Jakarta"},
57     {"KI", "Pacific/Tarawa"},
58     {"KZ", "Asia/Almaty"},
59     {"MH", "Pacific/Majuro"},
60     {"MN", "Asia/Ulaanbaatar"},
61     {"MX", "America/Mexico_City"},
62     {"MY", "Asia/Kuala_Lumpur"},
63     {"NZ", "Pacific/Auckland"},
64     {"PF", "Pacific/Tahiti"},
65     {"PG", "Pacific/Port_Moresby"},
66     {"PS", "Asia/Gaza"},
67     {"PT", "Europe/Lisbon"},
68     {"RU", "Europe/Moscow"},
69     {"UA", "Europe/Kiev"},
70     {"UM", "Pacific/Wake"},
71     {"US", "America/New_York"},
72     {"UZ", "Asia/Tashkent"},
73 };
74 
75 bool ZoneUtil::icuInitialized = ZoneUtil::Init();
76 
GetDefaultZone(const string & country)77 string ZoneUtil::GetDefaultZone(const string &country)
78 {
79     string temp(country);
80     for (size_t i = 0; i < temp.size(); i++) {
81         temp[i] = (char)toupper(temp[i]);
82     }
83     if (defaultMap.find(temp) != defaultMap.end()) {
84         return defaultMap[temp];
85     }
86     string ret;
87     StringEnumeration *strEnum = TimeZone::createEnumeration(temp.c_str());
88     GetString(strEnum, ret);
89     if (strEnum != nullptr) {
90         delete strEnum;
91     }
92     return ret;
93 }
94 
GetDefaultZone(const int32_t number)95 string ZoneUtil::GetDefaultZone(const int32_t number)
96 {
97     using i18n::phonenumbers::PhoneNumberUtil;
98     string *region_code = new(nothrow) string();
99     if (!region_code) {
100         return "";
101     }
102     PhoneNumberUtil* phoneUtil = PhoneNumberUtil::GetInstance();
103     phoneUtil->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     using i18n::phonenumbers::PhoneNumberUtil;
132     string *region_code = new(nothrow) string();
133     if (!region_code) {
134         return "";
135     }
136     PhoneNumberUtil* phoneUtil = PhoneNumberUtil::GetInstance();
137     phoneUtil->GetRegionCodeForCountryCode(number, region_code);
138     if (!region_code) {
139         return "";
140     }
141     string ret = GetDefaultZone(*region_code, offset);
142     delete region_code;
143     return ret;
144 }
145 
GetZoneList(const string country,vector<string> & retVec)146 void ZoneUtil::GetZoneList(const string country, vector<string> &retVec)
147 {
148     StringEnumeration *strEnum = TimeZone::createEnumeration(country.c_str());
149     GetList(strEnum, retVec);
150     if (strEnum != nullptr) {
151         delete strEnum;
152         strEnum = nullptr;
153     }
154 }
155 
GetZoneList(const string country,const int32_t offset,vector<string> & retVec)156 void ZoneUtil::GetZoneList(const string country, const int32_t offset, vector<string> &retVec)
157 {
158     UErrorCode status = U_ZERO_ERROR;
159     StringEnumeration *strEnum =
160         TimeZone::createTimeZoneIDEnumeration(UCAL_ZONE_TYPE_ANY, country.c_str(), &offset, status);
161     if (status != U_ZERO_ERROR) {
162         delete strEnum;
163         strEnum = nullptr;
164         return;
165     }
166     GetList(strEnum, retVec);
167     if (strEnum != nullptr) {
168         delete strEnum;
169         strEnum = nullptr;
170     }
171 }
172 
GetString(StringEnumeration * strEnum,string & ret)173 void ZoneUtil::GetString(StringEnumeration *strEnum, string& ret)
174 {
175     UErrorCode status = U_ZERO_ERROR;
176     UnicodeString uniString;
177     if (!strEnum) {
178         return;
179     }
180     int32_t count = strEnum->count(status);
181     if ((status != U_ZERO_ERROR) || count <= 0) {
182         return;
183     }
184     const UnicodeString *uniStr = strEnum->snext(status);
185     if ((status != U_ZERO_ERROR) || (!uniStr)) {
186         return;
187     }
188     UnicodeString canonicalUnistring;
189     TimeZone::getCanonicalID(*uniStr, canonicalUnistring, status);
190     if (status != U_ZERO_ERROR) {
191         return;
192     }
193     canonicalUnistring.toUTF8String(ret);
194     return;
195 }
196 
GetList(StringEnumeration * strEnum,vector<string> & retVec)197 void ZoneUtil::GetList(StringEnumeration *strEnum, vector<string> &retVec)
198 {
199     if (!strEnum) {
200         return;
201     }
202     UErrorCode status = U_ZERO_ERROR;
203     int32_t count = strEnum->count(status);
204     if (count <= 0 || status != U_ZERO_ERROR) {
205         return;
206     }
207     while (count > 0) {
208         const UnicodeString *uniStr = strEnum->snext(status);
209         if ((!uniStr) || (status != U_ZERO_ERROR)) {
210             retVec.clear();
211             break;
212         }
213         UnicodeString canonicalUnistring;
214         TimeZone::getCanonicalID(*uniStr, canonicalUnistring, status);
215         if (status != U_ZERO_ERROR) {
216             retVec.clear();
217             break;
218         }
219         string canonicalString = "";
220         canonicalUnistring.toUTF8String(canonicalString);
221         if ((canonicalString != "") && (find(retVec.begin(), retVec.end(), canonicalString) == retVec.end())) {
222             retVec.push_back(canonicalString);
223         }
224         --count;
225     }
226     return;
227 }
228 
Init()229 bool ZoneUtil::Init()
230 {
231     SetHwIcuDirectory();
232     return true;
233 }
234 
LookupTimezoneByCountryAndNITZ(std::string & region,NITZData & nitzData)235 CountryResult ZoneUtil::LookupTimezoneByCountryAndNITZ(std::string &region, NITZData &nitzData)
236 {
237     std::vector<std::string> zones;
238     std::string defaultTimezone;
239     std::string systemTimezone = ReadSystemParameter(TIMEZONE_KEY, SYS_PARAM_LEN);
240     if (systemTimezone.length() == 0) {
241         systemTimezone = DEFAULT_TIMEZONE;
242     }
243     if (CheckFileExist()) {
244         bool isBoosted = false;
245         HILOG_INFO_I18N("ZoneUtil::LookupTimezoneByCountryAndNITZ use tzlookup.xml");
246         GetCountryZones(region, defaultTimezone, isBoosted, zones);
247     } else {
248         HILOG_INFO_I18N("ZoneUtil::LookupTimezoneByCountryAndNITZ use icu data");
249         GetICUCountryZones(region, zones, defaultTimezone);
250     }
251     return Match(zones, nitzData, systemTimezone);
252 }
253 
LookupTimezoneByNITZ(NITZData & nitzData)254 CountryResult ZoneUtil::LookupTimezoneByNITZ(NITZData &nitzData)
255 {
256     std::string systemTimezone = ReadSystemParameter(TIMEZONE_KEY, SYS_PARAM_LEN);
257     if (systemTimezone.length() == 0) {
258         systemTimezone = DEFAULT_TIMEZONE;
259     }
260     I18nErrorCode status = I18nErrorCode::SUCCESS;
261     std::set<std::string> icuTimezones = I18nTimeZone::GetAvailableIDs(status);
262     if (status != I18nErrorCode::SUCCESS) {
263         HILOG_ERROR_I18N("ZoneUtil::LookupTimezoneByNITZ can not get icu data");
264     }
265     std::vector<std::string> validZones;
266     for (auto it = icuTimezones.begin(); it != icuTimezones.end(); ++it) {
267         validZones.push_back(*it);
268     }
269 
270     CountryResult result = Match(validZones, nitzData, systemTimezone);
271     if (result.timezoneId.length() == 0 && nitzData.isDST >= 0) {
272         NITZData newNITZData = { -1, nitzData.totalOffset, nitzData.currentMillis };  // -1 means not consider DST
273         result = Match(validZones, newNITZData, systemTimezone);
274     }
275     return result;
276 }
277 
LookupTimezoneByCountry(std::string & region,int64_t currentMillis)278 CountryResult ZoneUtil::LookupTimezoneByCountry(std::string &region, int64_t currentMillis)
279 {
280     std::vector<std::string> zones;
281     bool isBoosted = false;
282     std::string defaultTimezone;
283     CountryResult result = { true, MatchQuality::DEFAULT_BOOSTED, defaultTimezone };
284     if (CheckFileExist()) {
285         HILOG_INFO_I18N("ZoneUtil::LookupTimezoneByCountry use tzlookup.xml");
286         GetCountryZones(region, defaultTimezone, isBoosted, zones);
287         if (defaultTimezone.length() == 0) {
288             HILOG_ERROR_I18N("ZoneUtil::LookupTimezoneByCountry can't find default timezone for region %{public}s",
289                 region.c_str());
290         }
291     } else {
292         HILOG_INFO_I18N("ZoneUtil::LookupTimezoneByCountry use icu data");
293         GetICUCountryZones(region, zones, defaultTimezone);
294     }
295     result.timezoneId = defaultTimezone;
296     if (isBoosted) {
297         return result;
298     }
299     if (zones.size() == 0) {
300         result.quality = MatchQuality::MULTIPLE_ZONES_DIFFERENT_OFFSET;
301     } else if (zones.size() == 1) {
302         result.quality = MatchQuality::SINGLE_ZONE;
303     } else if (CheckSameDstOffset(zones, defaultTimezone, currentMillis)) {
304         result.quality = MatchQuality::MULTIPLE_ZONES_SAME_OFFSET;
305     } else {
306         result.quality = MatchQuality::MULTIPLE_ZONES_DIFFERENT_OFFSET;
307     }
308     return result;
309 }
310 
CheckFileExist()311 bool ZoneUtil::CheckFileExist()
312 {
313     using std::filesystem::directory_iterator;
314     struct stat s;
315     if (stat(COUNTRY_ZONE_DATA_PATH, &s) == 0) {
316         return true;
317     } else {
318         return false;
319     }
320 }
321 
GetCountryZones(std::string & region,std::string & defaultTimzone,bool & isBoosted,std::vector<std::string> & zones)322 void ZoneUtil::GetCountryZones(std::string &region, std::string &defaultTimzone, bool &isBoosted,
323     std::vector<std::string> &zones)
324 {
325     xmlKeepBlanksDefault(0);
326     xmlDocPtr doc = xmlParseFile(COUNTRY_ZONE_DATA_PATH);
327     if (!doc) {
328         HILOG_ERROR_I18N("ZoneUtil::GetCountryZones can not open tzlookup.xml");
329         return;
330     }
331     xmlNodePtr cur = xmlDocGetRootElement(doc);
332     if (!cur || xmlStrcmp(cur->name, reinterpret_cast<const xmlChar *>(ROOT_TAG)) != 0) {
333         xmlFreeDoc(doc);
334         HILOG_ERROR_I18N("ZoneUtil::GetCountryZones invalid Root_tag");
335         return;
336     }
337     cur = cur->xmlChildrenNode;
338     xmlNodePtr value;
339     bool findCountry = false;
340     while (cur != nullptr && xmlStrcmp(cur->name, reinterpret_cast<const xmlChar *>(SECOND_TAG)) == 0) {
341         value = cur->xmlChildrenNode;
342         if (xmlStrcmp(value->name, reinterpret_cast<const xmlChar*>(CODE_TAG)) != 0) {
343             xmlFreeDoc(doc);
344             HILOG_ERROR_I18N("ZoneUtil::GetCountryZones invalid code_tag");
345             return;
346         }
347         xmlChar *codePtr = xmlNodeGetContent(value);
348         if (codePtr == nullptr) {
349             cur = cur->next;
350             continue;
351         }
352         if (strcmp(region.c_str(), reinterpret_cast<const char*>(codePtr)) == 0) {
353             findCountry = true;
354             xmlFree(codePtr);
355             break;
356         } else {
357             xmlFree(codePtr);
358             cur = cur->next;
359             continue;
360         }
361     }
362     if (findCountry) {
363         value = value->next;
364         GetDefaultAndBoost(value, defaultTimzone, isBoosted, zones);
365     }
366     xmlFreeDoc(doc);
367     return;
368 }
369 
GetDefaultAndBoost(xmlNodePtr & value,std::string & defaultTimezone,bool & isBoosted,std::vector<std::string> & zones)370 void ZoneUtil::GetDefaultAndBoost(xmlNodePtr &value, std::string &defaultTimezone, bool &isBoosted,
371     std::vector<std::string> &zones)
372 {
373     if (value == nullptr || xmlStrcmp(value->name, reinterpret_cast<const xmlChar*>(DEFAULT_TAG)) != 0) {
374         HILOG_ERROR_I18N("ZoneUtil::GetDefaultAndBoost invalid default_tag");
375         return;
376     }
377     xmlChar *defaultPtr = xmlNodeGetContent(value);
378     if (defaultPtr != nullptr) {
379         defaultTimezone = reinterpret_cast<const char*>(defaultPtr);
380         xmlFree(defaultPtr);
381     }
382     value = value->next;
383     if (value == nullptr) {
384         HILOG_ERROR_I18N("ZoneUtil::GetDefaultAndBoost doesn't contains id");
385         return;
386     }
387     if (xmlStrcmp(value->name, reinterpret_cast<const xmlChar *>(BOOSTED_TAG)) == 0) {
388         isBoosted = true;
389         value = value->next;
390     } else {
391         isBoosted = false;
392     }
393     GetTimezones(value, zones);
394 }
395 
GetTimezones(xmlNodePtr & value,std::vector<std::string> & zones)396 void ZoneUtil::GetTimezones(xmlNodePtr &value, std::vector<std::string> &zones)
397 {
398     if (xmlStrcmp(value->name, reinterpret_cast<const xmlChar *>(TIMEZONES_TAG)) != 0) {
399         HILOG_ERROR_I18N("ZoneUtil::GetTimezones invalid timezones_tag");
400         return;
401     }
402     value = value->xmlChildrenNode;
403     while (value != nullptr) {
404         if (xmlStrcmp(value->name, reinterpret_cast<const xmlChar *>(ID_TAG)) != 0) {
405             HILOG_ERROR_I18N("ZoneUtil::GetTimezones invalid id_tag");
406             return;
407         }
408         xmlChar *idPtr = xmlNodeGetContent(value);
409         if (idPtr != nullptr) {
410             zones.push_back(reinterpret_cast<const char*>(idPtr));
411             xmlFree(idPtr);
412         }
413         value = value->next;
414     }
415 }
416 
GetICUCountryZones(std::string & region,std::vector<std::string> & zones,std::string & defaultTimezone)417 void ZoneUtil::GetICUCountryZones(std::string &region, std::vector<std::string> &zones, std::string &defaultTimezone)
418 {
419     I18nErrorCode errorCode = I18nErrorCode::SUCCESS;
420     std::set<std::string> validZoneIds = I18nTimeZone::GetAvailableIDs(errorCode);
421     if (errorCode != I18nErrorCode::SUCCESS) {
422         HILOG_ERROR_I18N("ZoneUtil::GetICUCountryZones can not get icu data");
423     }
424     std::set<std::string> countryZoneIds;
425     StringEnumeration *strEnum = TimeZone::createEnumeration(region.c_str());
426     UErrorCode status = U_ZERO_ERROR;
427     const UnicodeString *timezoneIdUStr = strEnum->snext(status);
428     while (timezoneIdUStr != nullptr && U_SUCCESS(status)) {
429         UnicodeString canonicalUnistring;
430         TimeZone::getCanonicalID(*timezoneIdUStr, canonicalUnistring, status);
431         std::string timezoneId;
432         canonicalUnistring.toUTF8String(timezoneId);
433         if (validZoneIds.find(timezoneId) != validZoneIds.end()) {
434             countryZoneIds.insert(timezoneId);
435         }
436         timezoneIdUStr = strEnum->snext(status);
437     }
438     for (auto it = countryZoneIds.begin(); it != countryZoneIds.end(); ++it) {
439         zones.push_back(*it);
440     }
441     if (defaultMap.find(region) != defaultMap.end()) {
442         defaultTimezone = defaultMap[region];
443     } else {
444         if (zones.size() > 0) {
445             defaultTimezone = zones[0];
446         }
447     }
448 }
449 
Match(std::vector<std::string> & zones,NITZData & nitzData,std::string & systemTimezone)450 CountryResult ZoneUtil::Match(std::vector<std::string> &zones, NITZData &nitzData, std::string &systemTimezone)
451 {
452     bool isOnlyMatch = true;
453     std::string matchedZoneId;
454     bool local = false;
455     bool useSystemTimezone = false;
456     for (size_t i = 0; i < zones.size(); i++) {
457         std::string zoneId = zones[i];
458         UnicodeString unicodeZoneID(zoneId.data(), zoneId.length());
459         TimeZone *timezone = TimeZone::createTimeZone(unicodeZoneID);
460         int32_t rawOffset;
461         int32_t dstOffset;
462         UErrorCode status = UErrorCode::U_ZERO_ERROR;
463         timezone->getOffset(nitzData.currentMillis, static_cast<UBool>(local), rawOffset, dstOffset, status);
464         if ((nitzData.totalOffset - rawOffset == dstOffset) &&
465             (nitzData.isDST < 0 || nitzData.isDST == (dstOffset != 0))) {
466             if (matchedZoneId.length() == 0) {
467                 matchedZoneId = zoneId;
468             } else {
469                 isOnlyMatch = false;
470             }
471             if (strcmp(zoneId.c_str(), systemTimezone.c_str()) == 0) {
472                 matchedZoneId = systemTimezone;
473                 useSystemTimezone = true;
474             }
475             if (!isOnlyMatch && useSystemTimezone) {
476                 break;
477             }
478         }
479     }
480     CountryResult result = {isOnlyMatch, MatchQuality::DEFAULT_BOOSTED, matchedZoneId};
481     return result;
482 }
483 
CheckSameDstOffset(std::vector<std::string> & zones,std::string & defaultTimezoneId,int64_t currentMillis)484 bool ZoneUtil::CheckSameDstOffset(std::vector<std::string> &zones, std::string &defaultTimezoneId,
485     int64_t currentMillis)
486 {
487     UnicodeString defaultID(defaultTimezoneId.data(), defaultTimezoneId.length());
488     TimeZone *defaultTimezone = TimeZone::createTimeZone(defaultID);
489     int32_t rawOffset = 0;
490     int32_t dstOffset = 0;
491     bool local = false;
492     UErrorCode status = U_ZERO_ERROR;
493     defaultTimezone->getOffset(currentMillis, (UBool)local, rawOffset, dstOffset, status);
494     if (U_FAILURE(status)) {
495         HILOG_ERROR_I18N("ZoneUtil::CheckSameDstOffset can not get timezone defaultID offset");
496         return false;
497     }
498     int32_t totalOffset = rawOffset + dstOffset;
499     for (size_t i = 0; i < zones.size(); i++) {
500         UnicodeString unicodeZoneID(zones[i].data(), zones[i].length());
501         TimeZone *timezone = TimeZone::createTimeZone(unicodeZoneID);
502         timezone->getOffset(currentMillis, (UBool)local, rawOffset, dstOffset, status);
503         if (U_FAILURE(status)) {
504             HILOG_ERROR_I18N("ZoneUtil::CheckSameDstOffset can not get timezone unicodeZoneID offset");
505             return false;
506         }
507         if (totalOffset - rawOffset != dstOffset) {
508             return false;
509         }
510     }
511     return true;
512 }
513