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#include "character.h"
16#include "error_util.h"
17#include "i18n_hilog.h"
18#include "i18n_timezone_addon.h"
19#include "js_utils.h"
20#include "variable_convertor.h"
21
22namespace OHOS {
23namespace Global {
24namespace I18n {
25static thread_local napi_ref* g_timezoneConstructor = nullptr;
26
27I18nTimeZoneAddon::I18nTimeZoneAddon() {}
28
29I18nTimeZoneAddon::~I18nTimeZoneAddon() {}
30
31void I18nTimeZoneAddon::Destructor(napi_env env, void *nativeObject, void *hint)
32{
33    if (!nativeObject) {
34        return;
35    }
36    delete reinterpret_cast<I18nTimeZoneAddon *>(nativeObject);
37    nativeObject = nullptr;
38}
39
40napi_value I18nTimeZoneAddon::GetI18nTimeZone(napi_env env, napi_callback_info info)
41{
42    size_t argc = 1;
43    napi_value argv[1] = { nullptr };
44    napi_value thisVar = nullptr;
45    void *data = nullptr;
46    napi_get_cb_info(env, info, &argc, argv, &thisVar, &data);
47    if (!VariableConvertor::CheckNapiValueType(env, argv[0])) {
48        napi_create_string_utf8(env, "", NAPI_AUTO_LENGTH, &argv[0]);
49    }
50    return StaticGetTimeZone(env, argv, true);
51}
52
53napi_value I18nTimeZoneAddon::InitI18nTimeZone(napi_env env, napi_value exports)
54{
55    napi_property_descriptor properties[] = {
56        DECLARE_NAPI_FUNCTION("getID", GetID),
57        DECLARE_NAPI_FUNCTION("getDisplayName", GetTimeZoneDisplayName),
58        DECLARE_NAPI_FUNCTION("getRawOffset", GetRawOffset),
59        DECLARE_NAPI_FUNCTION("getOffset", GetOffset),
60    };
61    napi_value constructor = nullptr;
62    napi_status status = napi_define_class(env, "TimeZone", NAPI_AUTO_LENGTH, I18nTimeZoneConstructor, nullptr,
63        sizeof(properties) / sizeof(napi_property_descriptor), properties, &constructor);
64    if (status != napi_ok) {
65        HILOG_ERROR_I18N("InitI18nTimeZone: Failed to define class TimeZone at Init");
66        return nullptr;
67    }
68    exports = I18nTimeZoneAddon::InitTimeZone(env, exports);
69    g_timezoneConstructor = new (std::nothrow) napi_ref;
70    if (!g_timezoneConstructor) {
71        HILOG_ERROR_I18N("InitI18nTimeZone: Failed to create TimeZone ref at init");
72        return nullptr;
73    }
74    status = napi_create_reference(env, constructor, 1, g_timezoneConstructor);
75    if (status != napi_ok) {
76        HILOG_ERROR_I18N("InitI18nTimeZone: Failed to create reference g_timezoneConstructor at init");
77        return nullptr;
78    }
79    return exports;
80}
81
82napi_value I18nTimeZoneAddon::InitTimeZone(napi_env env, napi_value exports)
83{
84    napi_property_descriptor properties[] = {
85        DECLARE_NAPI_STATIC_FUNCTION("getAvailableIDs", GetAvailableTimezoneIDs),
86        DECLARE_NAPI_STATIC_FUNCTION("getAvailableZoneCityIDs", GetAvailableZoneCityIDs),
87        DECLARE_NAPI_STATIC_FUNCTION("getCityDisplayName", GetCityDisplayName),
88        DECLARE_NAPI_STATIC_FUNCTION("getTimezoneFromCity", GetTimezoneFromCity),
89        DECLARE_NAPI_STATIC_FUNCTION("getTimezonesByLocation", GetTimezonesByLocation)
90    };
91    napi_value constructor = nullptr;
92    napi_status status = napi_define_class(env, "I18nTimeZone", NAPI_AUTO_LENGTH, JSUtils::DefaultConstructor, nullptr,
93        sizeof(properties) / sizeof(napi_property_descriptor), properties, &constructor);
94    if (status != napi_ok) {
95        HILOG_ERROR_I18N("InitTimeZone: Failed to define class TimeZone.");
96        return nullptr;
97    }
98    status = napi_set_named_property(env, exports, "TimeZone", constructor);
99    if (status != napi_ok) {
100        HILOG_ERROR_I18N("InitTimeZone: Set property failed When InitTimeZone.");
101        return nullptr;
102    }
103    return exports;
104}
105
106napi_value I18nTimeZoneAddon::I18nTimeZoneConstructor(napi_env env, napi_callback_info info)
107{
108    size_t argc = 2;
109    napi_value argv[2] = { nullptr };
110    napi_value thisVar = nullptr;
111    void *data = nullptr;
112    napi_status status = napi_get_cb_info(env, info, &argc, argv, &thisVar, &data);
113    if (status != napi_ok) {
114        return nullptr;
115    }
116    std::string zoneID = "";
117    napi_valuetype valueType = napi_valuetype::napi_undefined;
118    if (argc > 0) {
119        napi_typeof(env, argv[0], &valueType);
120        if (valueType != napi_valuetype::napi_string) {
121            return nullptr;
122        }
123        int32_t code = 0;
124        zoneID = VariableConvertor::GetString(env, argv[0], code);
125        if (code != 0) {
126            return nullptr;
127        }
128    }
129    if (argc < FUNC_ARGS_COUNT) {
130        return nullptr;
131    }
132    napi_typeof(env, argv[1], &valueType);
133    if (valueType != napi_valuetype::napi_boolean) {
134        return nullptr;
135    }
136    bool isZoneID = false;
137    status = napi_get_value_bool(env, argv[1], &isZoneID);
138    if (status != napi_ok) {
139        return nullptr;
140    }
141    std::unique_ptr<I18nTimeZoneAddon> obj = std::make_unique<I18nTimeZoneAddon>();
142    status =
143        napi_wrap(env, thisVar, reinterpret_cast<void *>(obj.get()), I18nTimeZoneAddon::Destructor, nullptr, nullptr);
144    if (status != napi_ok) {
145        return nullptr;
146    }
147    obj->timezone_ = I18nTimeZone::CreateInstance(zoneID, isZoneID);
148    if (!obj->timezone_) {
149        return nullptr;
150    }
151    obj.release();
152    return thisVar;
153}
154
155napi_value I18nTimeZoneAddon::GetAvailableTimezoneIDs(napi_env env, napi_callback_info info)
156{
157    I18nErrorCode errorCode = I18nErrorCode::SUCCESS;
158    std::set<std::string> timezoneIDs = I18nTimeZone::GetAvailableIDs(errorCode);
159    if (errorCode != I18nErrorCode::SUCCESS) {
160        return nullptr;
161    }
162    napi_value result = nullptr;
163    napi_status status = napi_create_array_with_length(env, timezoneIDs.size(), &result);
164    if (status != napi_ok) {
165        HILOG_ERROR_I18N("GetAvailableTimezoneIDs: Failed to create array");
166        return nullptr;
167    }
168    size_t index = 0;
169    for (std::set<std::string>::iterator it = timezoneIDs.begin(); it != timezoneIDs.end(); ++it) {
170        napi_value value = nullptr;
171        status = napi_create_string_utf8(env, (*it).c_str(), NAPI_AUTO_LENGTH, &value);
172        if (status != napi_ok) {
173            HILOG_ERROR_I18N("Failed to create string item");
174            return nullptr;
175        }
176        status = napi_set_element(env, result, index, value);
177        if (status != napi_ok) {
178            HILOG_ERROR_I18N("Failed to set array item");
179            return nullptr;
180        }
181        ++index;
182    }
183    return result;
184}
185
186napi_value I18nTimeZoneAddon::GetAvailableZoneCityIDs(napi_env env, napi_callback_info info)
187{
188    std::set<std::string> cityIDs = I18nTimeZone::GetAvailableZoneCityIDs();
189    napi_value result = nullptr;
190    napi_status status = napi_create_array_with_length(env, cityIDs.size(), &result);
191    if (status != napi_ok) {
192        HILOG_ERROR_I18N("GetAvailableZoneCityIDs: Failed to create array");
193        return nullptr;
194    }
195    size_t index = 0;
196    for (auto it = cityIDs.begin(); it != cityIDs.end(); ++it) {
197        napi_value value = nullptr;
198        status = napi_create_string_utf8(env, (*it).c_str(), NAPI_AUTO_LENGTH, &value);
199        if (status != napi_ok) {
200            HILOG_ERROR_I18N("GetAvailableZoneCityIDs: Failed to create string item");
201            return nullptr;
202        }
203        status = napi_set_element(env, result, index, value);
204        if (status != napi_ok) {
205            HILOG_ERROR_I18N("GetAvailableZoneCityIDs: Failed to set array item");
206            return nullptr;
207        }
208        ++index;
209    }
210    return result;
211}
212
213napi_value I18nTimeZoneAddon::GetCityDisplayName(napi_env env, napi_callback_info info)
214{
215    size_t argc = 2;
216    napi_value argv[2] = { 0 };
217    napi_value thisVar = nullptr;
218    void *data = nullptr;
219    napi_status status = napi_get_cb_info(env, info, &argc, argv, &thisVar, &data);
220    if (status != napi_ok) {
221        return nullptr;
222    }
223    if (argc < FUNC_ARGS_COUNT) {
224        return nullptr;
225    }
226    napi_valuetype valueType = napi_valuetype::napi_undefined;
227    napi_typeof(env, argv[0], &valueType);
228    if (valueType != napi_valuetype::napi_string) {
229        HILOG_ERROR_I18N("GetCityDisplayName: Invalid parameter type");
230        return nullptr;
231    }
232    int32_t code = 0;
233    std::string cityID = VariableConvertor::GetString(env, argv[0], code);
234    if (code != 0) {
235        return nullptr;
236    }
237    std::string locale = VariableConvertor::GetString(env, argv[1], code);
238    if (code != 0) {
239        return nullptr;
240    }
241    std::string name = I18nTimeZone::GetCityDisplayName(cityID, locale);
242    napi_value result = nullptr;
243    status = napi_create_string_utf8(env, name.c_str(), NAPI_AUTO_LENGTH, &result);
244    if (status != napi_ok) {
245        return nullptr;
246    }
247    return result;
248}
249
250napi_value I18nTimeZoneAddon::GetTimezoneFromCity(napi_env env, napi_callback_info info)
251{
252    size_t argc = 1;
253    napi_value argv[1] = { nullptr };
254    napi_value thisVar = nullptr;
255    void *data = nullptr;
256    napi_get_cb_info(env, info, &argc, argv, &thisVar, &data);
257    return StaticGetTimeZone(env, argv, false);
258}
259
260napi_value I18nTimeZoneAddon::GetTimezonesByLocation(napi_env env, napi_callback_info info)
261{
262    size_t argc = 2;
263    napi_value argv[2] = {0, 0};
264    napi_value thisVar = nullptr;
265    void *data = nullptr;
266    napi_status status = napi_get_cb_info(env, info, &argc, argv, &thisVar, &data);
267    if (status != napi_ok) {
268        return nullptr;
269    }
270    if (argc < FUNC_ARGS_COUNT) {
271        HILOG_ERROR_I18N("GetTimezonesByLocation: Missing parameter");
272        ErrorUtil::NapiThrow(env, I18N_NOT_FOUND, "longitude or latitude", "", true);
273        return nullptr;
274    }
275    double x;
276    double y;
277    VariableConvertor::VerifyType(env, "longitude", "number", argv[0]);
278    VariableConvertor::VerifyType(env, "latitude", "number", argv[1]);
279    if (!CheckLongitudeTypeAndScope(env, argv[0], x) ||
280        !CheckLatitudeTypeAndScope(env, argv[1], y)) {
281        ErrorUtil::NapiThrow(env, I18N_NOT_VALID, "longitude or latitude", "a valid value", true);
282        return nullptr;
283    }
284    napi_value timezoneList = nullptr;
285    napi_create_array(env, &timezoneList);
286    std::vector<std::string> tempList = I18nTimeZone::GetTimezoneIdByLocation(x, y);
287    for (size_t i = 0; i < tempList.size(); i++) {
288        napi_value timezoneId = nullptr;
289        status = napi_create_string_utf8(env, tempList[i].c_str(), NAPI_AUTO_LENGTH, &timezoneId);
290        if (status != napi_ok) {
291            return nullptr;
292        }
293        napi_value argTimeZoneId[1] = { timezoneId };
294        napi_value timezone = StaticGetTimeZone(env, argTimeZoneId, true);
295        status = napi_set_element(env, timezoneList, i, timezone);
296        if (status != napi_ok) {
297            return nullptr;
298        }
299    }
300
301    return timezoneList;
302}
303
304bool I18nTimeZoneAddon::CheckLongitudeTypeAndScope(napi_env env, napi_value argv, double &x)
305{
306    napi_status status = napi_get_value_double(env, argv, &x);
307    if (status != napi_ok) {
308        HILOG_ERROR_I18N("GetTimezonesByLocation: Parse first argument x failed");
309        return false;
310    }
311    // -180 and 179.9 is the scope of longitude
312    if (x < -180 || x > 179.9) {
313        HILOG_ERROR_I18N("GetTimezonesByLocation: Args x exceed it's scope.");
314        return false;
315    }
316    return true;
317}
318
319bool I18nTimeZoneAddon::CheckLatitudeTypeAndScope(napi_env env, napi_value argv, double &y)
320{
321    napi_status status = napi_get_value_double(env, argv, &y);
322    if (status != napi_ok) {
323        HILOG_ERROR_I18N("GetTimezonesByLocation: Parse second argument y failed");
324        return false;
325    }
326    // -90 and 89.9 is the scope of latitude
327    if (y < -90 || y > 89.9) {
328        HILOG_ERROR_I18N("GetTimezonesByLocation: Args y exceed it's scope.");
329        return false;
330    }
331    return true;
332}
333
334napi_value I18nTimeZoneAddon::GetID(napi_env env, napi_callback_info info)
335{
336    size_t argc = 0;
337    napi_value *argv = nullptr;
338    napi_value thisVar = nullptr;
339    void *data = nullptr;
340    napi_get_cb_info(env, info, &argc, argv, &thisVar, &data);
341    I18nTimeZoneAddon *obj = nullptr;
342    napi_status status = napi_unwrap(env, thisVar, reinterpret_cast<void **>(&obj));
343    if (status != napi_ok || !obj || !obj->timezone_) {
344        HILOG_ERROR_I18N("GetID: Get TimeZone object failed");
345        return nullptr;
346    }
347    std::string result = obj->timezone_->GetID();
348    napi_value value = nullptr;
349    status = napi_create_string_utf8(env, result.c_str(), NAPI_AUTO_LENGTH, &value);
350    if (status != napi_ok) {
351        HILOG_ERROR_I18N("GetID: Create result failed");
352        return nullptr;
353    }
354    return value;
355}
356
357napi_value I18nTimeZoneAddon::GetTimeZoneDisplayName(napi_env env, napi_callback_info info)
358{
359    size_t argc = 2;
360    napi_value argv[2] = { 0 };
361    napi_value thisVar = nullptr;
362    void *data = nullptr;
363    napi_status status = napi_get_cb_info(env, info, &argc, argv, &thisVar, &data);
364    if (status != napi_ok) {
365        return nullptr;
366    }
367
368    I18nTimeZoneAddon *obj = nullptr;
369    status = napi_unwrap(env, thisVar, reinterpret_cast<void **>(&obj));
370    if (status != napi_ok || !obj || !obj->timezone_) {
371        HILOG_ERROR_I18N("GetTimeZoneDisplayName: Get TimeZone object failed");
372        return nullptr;
373    }
374
375    std::string locale;
376    bool isDST = false;
377    int32_t parameterStatus = GetParameter(env, argv, locale, isDST);
378
379    std::string result;
380    if (parameterStatus == -1) {  // -1 represents Invalid parameter.
381        HILOG_ERROR_I18N("GetTimeZoneDisplayName: Parameter type does not match");
382        return nullptr;
383    } else if (parameterStatus == 0) {
384        result = obj->timezone_->GetDisplayName();
385    } else if (parameterStatus == 1) {  // 1 represents one string parameter.
386        result = obj->timezone_->GetDisplayName(locale);
387    } else if (parameterStatus == 2) {  // 2 represents one boolean parameter.
388        result = obj->timezone_->GetDisplayName(isDST);
389    } else {
390        result = obj->timezone_->GetDisplayName(locale, isDST);
391    }
392
393    napi_value value = nullptr;
394    status = napi_create_string_utf8(env, result.c_str(), NAPI_AUTO_LENGTH, &value);
395    if (status != napi_ok) {
396        HILOG_ERROR_I18N("GetTimeZoneDisplayName: Create result failed");
397        return nullptr;
398    }
399    return value;
400}
401
402napi_value I18nTimeZoneAddon::GetRawOffset(napi_env env, napi_callback_info info)
403{
404    size_t argc = 0;
405    napi_value *argv = nullptr;
406    napi_value thisVar = nullptr;
407    void *data = nullptr;
408    napi_get_cb_info(env, info, &argc, argv, &thisVar, &data);
409    I18nTimeZoneAddon *obj = nullptr;
410    napi_status status = napi_unwrap(env, thisVar, reinterpret_cast<void **>(&obj));
411    if (status != napi_ok || !obj || !obj->timezone_) {
412        HILOG_ERROR_I18N("GetRawOffset: Get TimeZone object failed");
413        return nullptr;
414    }
415    int32_t result = obj->timezone_->GetRawOffset();
416    napi_value value = nullptr;
417    status = napi_create_int32(env, result, &value);
418    if (status != napi_ok) {
419        HILOG_ERROR_I18N("GetRawOffset: Create result failed");
420        return nullptr;
421    }
422    return value;
423}
424
425napi_value I18nTimeZoneAddon::GetOffset(napi_env env, napi_callback_info info)
426{
427    size_t argc = 1;
428    napi_value argv[1] = { 0 };
429    napi_value thisVar = nullptr;
430    void *data = nullptr;
431    napi_status status = napi_get_cb_info(env, info, &argc, argv, &thisVar, &data);
432    if (status != napi_ok) {
433        return nullptr;
434    }
435
436    double date = 0;
437    if (VariableConvertor::CheckNapiValueType(env, argv[0])) {
438        napi_valuetype valueType = napi_valuetype::napi_undefined;
439        napi_typeof(env, argv[0], &valueType);
440        if (valueType != napi_valuetype::napi_number) {
441            HILOG_ERROR_I18N("GetOffset: Invalid parameter type");
442            return nullptr;
443        }
444        status = napi_get_value_double(env, argv[0], &date);
445        if (status != napi_ok) {
446            HILOG_ERROR_I18N("Get parameter date failed");
447            return nullptr;
448        }
449    } else {
450        auto time = std::chrono::system_clock::now();
451        auto since_epoch = time.time_since_epoch();
452        auto millis = std::chrono::duration_cast<std::chrono::milliseconds>(since_epoch);
453        date = (double)millis.count();
454    }
455
456    I18nTimeZoneAddon *obj = nullptr;
457    status = napi_unwrap(env, thisVar, reinterpret_cast<void **>(&obj));
458    if (status != napi_ok || !obj || !obj->timezone_) {
459        HILOG_ERROR_I18N("GetOffset: Get TimeZone object failed");
460        return nullptr;
461    }
462    int32_t result = obj->timezone_->GetOffset(date);
463    napi_value value = nullptr;
464    status = napi_create_int32(env, result, &value);
465    if (status != napi_ok) {
466        HILOG_ERROR_I18N("GetOffset: Create result failed");
467        return nullptr;
468    }
469    return value;
470}
471
472napi_value I18nTimeZoneAddon::StaticGetTimeZone(napi_env env, napi_value *argv, bool isZoneID)
473{
474    napi_value constructor = nullptr;
475    napi_status status = napi_get_reference_value(env, *g_timezoneConstructor, &constructor);
476    if (status != napi_ok) {
477        HILOG_ERROR_I18N("Failed to create reference at StaticGetTimeZone");
478        return nullptr;
479    }
480    napi_value newArgv[2] = { 0 };
481    newArgv[0] = argv[0];
482    status = napi_get_boolean(env, isZoneID, &newArgv[1]);
483    if (status != napi_ok) {
484        return nullptr;
485    }
486    napi_value result = nullptr;
487    status = napi_new_instance(env, constructor, 2, newArgv, &result); // 2 is parameter num
488    if (status != napi_ok) {
489        HILOG_ERROR_I18N("StaticGetTimeZone create instance failed");
490        return nullptr;
491    }
492    return result;
493}
494
495int32_t I18nTimeZoneAddon::GetParameter(napi_env env, napi_value *argv, std::string &localeStr, bool &isDST)
496{
497    napi_status status = napi_ok;
498    if (VariableConvertor::CheckNapiValueType(env, argv[1])) {
499        napi_valuetype valueType0 = napi_valuetype::napi_undefined;
500        napi_valuetype valueType1 = napi_valuetype::napi_undefined;
501        napi_typeof(env, argv[0], &valueType0);  // 0 represents first parameter
502        napi_typeof(env, argv[1], &valueType1);  // 1 represents second parameter
503        bool firstParamFlag = VariableConvertor::CheckNapiValueType(env, argv[0]);
504        if (valueType1 == napi_valuetype::napi_boolean) {
505            status = napi_get_value_bool(env, argv[1], &isDST);
506            if (status != napi_ok) {
507                return -1;  // -1 represents Invalid parameter.
508            } else if (!firstParamFlag) {
509                return 2;  // 2 represents one boolean parameter.
510            }
511            if (valueType0 == napi_valuetype::napi_string &&
512                 GetStringFromJS(env, argv[0], localeStr)) {
513                return 3;  // 3 represents one string parameter and one bool parameter.
514            }
515        }
516        return -1;  // -1 represents Invalid parameter.
517    }
518    return GetFirstParameter(env, argv[0], localeStr, isDST);
519}
520
521bool I18nTimeZoneAddon::GetStringFromJS(napi_env env, napi_value argv, std::string &jsString)
522{
523    size_t len = 0;
524    napi_status status = napi_get_value_string_utf8(env, argv, nullptr, 0, &len);
525    if (status != napi_ok) {
526        HILOG_ERROR_I18N("Failed to get string length");
527        return false;
528    }
529    std::vector<char> argvBuf(len + 1);
530    status = napi_get_value_string_utf8(env, argv, argvBuf.data(), len + 1, &len);
531    if (status != napi_ok) {
532        HILOG_ERROR_I18N("Failed to get string item");
533        return false;
534    }
535    jsString = argvBuf.data();
536    return true;
537}
538
539int32_t I18nTimeZoneAddon::GetFirstParameter(napi_env env, napi_value value, std::string &localeStr, bool &isDST)
540{
541    if (!VariableConvertor::CheckNapiValueType(env, value)) {
542        return 0;  // 0 represents no parameter.
543    } else {
544        napi_status status = napi_ok;
545        napi_valuetype valueType = napi_valuetype::napi_undefined;
546        napi_typeof(env, value, &valueType);
547        if (valueType == napi_valuetype::napi_string) {
548            bool valid = GetStringFromJS(env, value, localeStr);
549            // -1 represents Invalid parameter.
550            // 1 represents one string parameter.
551            return !valid ? -1 : 1;
552        } else if (valueType == napi_valuetype::napi_boolean) {
553            status = napi_get_value_bool(env, value, &isDST);
554            // -1 represents Invalid parameter.
555            // 2 represents one boolean parameter.
556            return (status != napi_ok) ? -1 : 2;
557        }
558        return -1;  // -1 represents Invalid parameter.
559    }
560}
561} // namespace I18n
562} // namespace Global
563} // namespace OHOS