• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2025 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 "js_relative_time_format_addon.h"
16 
17 #include <cmath>
18 #include "error_util.h"
19 #include "i18n_hilog.h"
20 #include "i18n_types.h"
21 #include "js_utils.h"
22 #include "locale_helper.h"
23 #include "utils.h"
24 
25 namespace OHOS {
26 namespace Global {
27 namespace I18n {
28 
29 std::unordered_map<std::string, std::vector<std::string>> JSRelativeTimeFormatAddon::optionAvaliableMap {
30     {"localeMatcher", {"lookup", "best fit"}},
31     {"numeric", {"always", "auto"}},
32     {"style", {"long", "short", "narrow"}},
33     {"unit", {"year", "years", "quarter", "quarters", "month", "months", "week", "weeks",
34             "day", "days", "hour", "hours", "minute", "minutes", "second", "seconds"}},
35 };
36 
JSRelativeTimeFormatAddon()37 JSRelativeTimeFormatAddon::JSRelativeTimeFormatAddon()
38 {
39 }
40 
~JSRelativeTimeFormatAddon()41 JSRelativeTimeFormatAddon::~JSRelativeTimeFormatAddon()
42 {
43 }
44 
Destructor(napi_env env,void * nativeObject,void * hint)45 void JSRelativeTimeFormatAddon::Destructor(napi_env env, void *nativeObject, void *hint)
46 {
47     if (!nativeObject) {
48         return;
49     }
50     delete reinterpret_cast<JSRelativeTimeFormatAddon *>(nativeObject);
51     nativeObject = nullptr;
52 }
53 
InitJsRelativeTimeFormat(napi_env env,napi_value exports)54 napi_value JSRelativeTimeFormatAddon::InitJsRelativeTimeFormat(napi_env env, napi_value exports)
55 {
56     napi_property_descriptor properties[] = {
57         DECLARE_NAPI_FUNCTION("format", Format),
58         DECLARE_NAPI_FUNCTION("formatToParts", FormatToParts),
59         DECLARE_NAPI_FUNCTION("resolvedOptions", GetNumberResolvedOptions),
60         DECLARE_NAPI_STATIC_FUNCTION("supportedLocalesOf", SupportedLocalesOf),
61     };
62     napi_value constructor = nullptr;
63     napi_status status = napi_define_class(env, "RelativeTimeFormat", NAPI_AUTO_LENGTH,
64         RelativeTimeFormatConstructor, nullptr,
65         sizeof(properties) / sizeof(napi_property_descriptor), properties, &constructor);
66     if (status != napi_ok) {
67         HILOG_ERROR_I18N("InitJsRelativeTimeFormat: Define class failed when init JsRelativeTimeFormat");
68         return nullptr;
69     }
70     status = napi_set_named_property(env, exports, "RelativeTimeFormat", constructor);
71     if (status != napi_ok) {
72         HILOG_ERROR_I18N("InitJsRelativeTimeFormat: Set property failed when init JsRelativeTimeFormat");
73         return nullptr;
74     }
75     return exports;
76 }
77 
RelativeTimeFormatConstructor(napi_env env,napi_callback_info info)78 napi_value JSRelativeTimeFormatAddon::RelativeTimeFormatConstructor(napi_env env, napi_callback_info info)
79 {
80     size_t argc = 2;
81     napi_value argv[2] = { nullptr };
82     napi_value thisVar = nullptr;
83     void *data = nullptr;
84     napi_status status = napi_get_cb_info(env, info, &argc, argv, &thisVar, &data);
85     if (status != napi_ok) {
86         HILOG_ERROR_I18N("RelativeTimeFormatConstructor: Get parameter info failed");
87         return nullptr;
88     }
89     napi_value newTarget;
90     status = napi_get_new_target(env, info, &newTarget);
91     if (status != napi_ok  || newTarget == nullptr) {
92         HILOG_ERROR_I18N("RelativeTimeFormatConstructor: Intl.RelativeTimeFormat requires new");
93         napi_throw_type_error(env, nullptr, "newTarget is undefined");
94         return nullptr;
95     }
96     JSRelativeTimeFormatAddon* obj = new (std::nothrow) JSRelativeTimeFormatAddon();
97     if (obj == nullptr) {
98         HILOG_ERROR_I18N("RelativeTimeFormatConstructor: Create JSRelativeTimeFormatAddon failed");
99         return nullptr;
100     }
101     std::vector<std::string> localeTags;
102     if (!obj->GetParameterLocales(env, localeTags, argc, argv)) {
103         delete obj;
104         return nullptr;
105     }
106     std::map<std::string, std::string> map = {};
107     if (argc > 1) {
108         GetRelativeTimeOptionValues(env, argv[1], map);
109     }
110     status = napi_wrap(env, thisVar, reinterpret_cast<void *>(obj), JSRelativeTimeFormatAddon::Destructor,
111         nullptr, nullptr);
112     if (status != napi_ok) {
113         delete obj;
114         HILOG_ERROR_I18N("RelativeTimeFormatConstructor: Wrap JSRelativeTimeFormatAddon failed");
115         return nullptr;
116     }
117     if (!obj->InitRelativeTimeFormatContext(env, info, localeTags, map)) {
118         HILOG_ERROR_I18N("Init RelativeTimeFormat failed");
119         return nullptr;
120     }
121     return thisVar;
122 }
123 
GetParameterLocales(napi_env env,std::vector<std::string> & localeTags,const size_t & argc,napi_value * argv)124 bool JSRelativeTimeFormatAddon::GetParameterLocales(napi_env env,
125     std::vector<std::string> &localeTags, const size_t &argc, napi_value* argv)
126 {
127     if (argc < 1) {
128         localeTags.push_back("en-US");
129         return true;
130     }
131     int32_t code = 0;
132     std::vector<std::string> localeArray = JSUtils::GetLocaleArray(env, argv[0], code);
133     if (localeArray.empty()) {
134         localeTags.push_back("en-US");
135         return true;
136     }
137     std::string checkResult = LocaleHelper::CheckParamLocales(localeArray);
138     if (!checkResult.empty()) {
139         ErrorUtil::NapiThrowUndefined(env, checkResult);
140         return false;
141     }
142     ErrorMessage errorMsg;
143     localeArray = RelativeTimeFormat::CanonicalizeLocales(localeArray, errorMsg);
144     if (errorMsg.type != ErrorType::NO_ERROR) {
145         napi_throw_range_error(env, nullptr, errorMsg.message.c_str());
146         return false;
147     }
148     localeTags.assign(localeArray.begin(), localeArray.end());
149     return true;
150 }
151 
GetRelativeTimeOptionValues(napi_env env,napi_value options,std::map<std::string,std::string> & map)152 void JSRelativeTimeFormatAddon::GetRelativeTimeOptionValues(napi_env env,
153     napi_value options, std::map<std::string, std::string> &map)
154 {
155     JSUtils::GetOptionValue(env, options, "localeMatcher", map);
156     CheckOptionValue(env, map, "localeMatcher");
157     JSUtils::GetOptionValue(env, options, "numeric", map);
158     CheckOptionValue(env, map, "numeric");
159     JSUtils::GetOptionValue(env, options, "style", map);
160     CheckOptionValue(env, map, "style");
161 }
162 
CheckOptionValue(napi_env env,std::map<std::string,std::string> & map,const std::string & optionName)163 void JSRelativeTimeFormatAddon::CheckOptionValue(napi_env env, std::map<std::string, std::string> &map,
164     const std::string &optionName)
165 {
166     if (optionAvaliableMap.find(optionName) == optionAvaliableMap.end()) {
167         return;
168     }
169     if (map.find(optionName) != map.end()) {
170         std::string optionValue = map[optionName];
171         CheckOptionValue(env, optionValue, optionName);
172     }
173 }
174 
CheckOptionValue(napi_env env,const std::string & optionValue,const std::string & optionName)175 void JSRelativeTimeFormatAddon::CheckOptionValue(napi_env env, const std::string &optionValue,
176     const std::string &optionName)
177 {
178     if (optionAvaliableMap.find(optionName) == optionAvaliableMap.end()) {
179         return;
180     }
181     std::vector<std::string> aviliableValues = optionAvaliableMap[optionName];
182     auto it = std::find(aviliableValues.begin(), aviliableValues.end(), optionValue);
183     if (it != aviliableValues.end()) {
184         return;
185     }
186     if (!ThrowRangeError(env)) {
187         HILOG_ERROR_I18N("JSRelativeTimeFormatAddon::CheckOptionValue:"
188             "throw error for %{public}s failed", optionName.c_str());
189         return;
190     }
191 }
192 
ThrowRangeError(napi_env env)193 bool JSRelativeTimeFormatAddon::ThrowRangeError(napi_env env)
194 {
195     const char* msg = "Value out of range for locale options property";
196     napi_status status = napi_throw_range_error(env, nullptr, msg);
197     if (status != napi_ok) {
198         HILOG_ERROR_I18N("JSRelativeTimeFormatAddon::ThrowRangeError: throw range error failed");
199         return false;
200     }
201     return true;
202 }
203 
InitRelativeTimeFormatContext(napi_env env,napi_callback_info info,std::vector<std::string> localeTags,std::map<std::string,std::string> & map)204 bool JSRelativeTimeFormatAddon::InitRelativeTimeFormatContext(napi_env env, napi_callback_info info,
205     std::vector<std::string> localeTags, std::map<std::string, std::string> &map)
206 {
207     relativetimefmt_ = std::make_shared<RelativeTimeFormat>(localeTags, map, true);
208     return relativetimefmt_ != nullptr;
209 }
210 
Format(napi_env env,napi_callback_info info)211 napi_value JSRelativeTimeFormatAddon::Format(napi_env env, napi_callback_info info)
212 {
213     size_t argc = 2;
214     napi_value argv[2] = { nullptr };
215     napi_value thisVar = nullptr;
216     napi_status status = napi_get_cb_info(env, info, &argc, argv, &thisVar, nullptr);
217     if (status != napi_ok) {
218         HILOG_ERROR_I18N("JSRelativeTimeFormatAddon::Format: Get parameter info failed");
219         return JSUtils::CreateEmptyString(env);
220     }
221     if (argc < 2) { // 2 is required parameter count
222         HILOG_ERROR_I18N("JSRelativeTimeFormatAddon::Format: parameter less then required");
223         return JSUtils::CreateEmptyString(env);
224     }
225     napi_valuetype valueType = napi_valuetype::napi_undefined;
226     status = napi_typeof(env, argv[0], &valueType);
227     if (status != napi_ok) {
228         HILOG_ERROR_I18N("JSRelativeTimeFormatAddon::Format: Get param type failed.");
229         return JSUtils::CreateEmptyString(env);
230     }
231     int32_t code = 0;
232     double number = JSUtils::GetDoubleFromNapiValue(env, argv[0], valueType, code);
233     if (code != 0) {
234         HILOG_ERROR_I18N("JSRelativeTimeFormatAddon::Format: Get number failed");
235         return JSUtils::CreateEmptyString(env);
236     }
237     std::string unit = JSUtils::GetString(env, argv[1], code);
238     if (code != 0) {
239         return JSUtils::CreateEmptyString(env);
240     }
241     CheckOptionValue(env, unit, "unit");
242     JSRelativeTimeFormatAddon *obj = nullptr;
243     status = napi_unwrap(env, thisVar, reinterpret_cast<void **>(&obj));
244     if (status != napi_ok || !obj || !obj->relativetimefmt_) {
245         HILOG_ERROR_I18N("JSRelativeTimeFormatAddon::Format: Get RelativeTimeFormat object failed");
246         return JSUtils::CreateEmptyString(env);
247     }
248     std::string value = obj->relativetimefmt_->Format(number, unit);
249     return JSUtils::CreateString(env, value);
250 }
251 
GetNumberResolvedOptions(napi_env env,napi_callback_info info)252 napi_value JSRelativeTimeFormatAddon::GetNumberResolvedOptions(napi_env env, napi_callback_info info)
253 {
254     napi_value thisVar = nullptr;
255     void *data = nullptr;
256     napi_status status = napi_get_cb_info(env, info, nullptr, nullptr, &thisVar, &data);
257     if (status != napi_ok) {
258         HILOG_ERROR_I18N("JSRelativeTimeFormatAddon::GetNumberResolvedOptions: Get parameter info failed");
259         return JSUtils::CreateEmptyObject(env);
260     }
261     JSRelativeTimeFormatAddon *obj = nullptr;
262     status = napi_unwrap(env, thisVar, reinterpret_cast<void **>(&obj));
263     if (status != napi_ok || !obj || !obj->relativetimefmt_) {
264         HILOG_ERROR_I18N("GetNumberResolvedOptions: Get RelativeTimeFormat object failed");
265         return JSUtils::CreateEmptyObject(env);
266     }
267     napi_value result = nullptr;
268     status = napi_create_object(env, &result);
269     if (status != napi_ok) {
270         HILOG_ERROR_I18N("JSRelativeTimeFormatAddon::GetNumberResolvedOptions: napi_create_object failed");
271         return JSUtils::CreateEmptyObject(env);
272     }
273     std::map<std::string, std::string> options = {};
274     obj->relativetimefmt_->GetResolvedOptions(options);
275     JSUtils::SetOptionProperties(env, result, options, "locale");
276     JSUtils::SetOptionProperties(env, result, options, "style");
277     JSUtils::SetOptionProperties(env, result, options, "numeric");
278     JSUtils::SetOptionProperties(env, result, options, "numberingSystem");
279     return result;
280 }
281 
FormatToParts(napi_env env,napi_callback_info info)282 napi_value JSRelativeTimeFormatAddon::FormatToParts(napi_env env, napi_callback_info info)
283 {
284     size_t argc = 2;
285     const size_t argsCount = 2; // 2 is required parameter count
286     napi_value argv[argsCount] = { nullptr };
287     napi_value thisVar = nullptr;
288     napi_status status = napi_get_cb_info(env, info, &argc, argv, &thisVar, nullptr);
289     if (status != napi_ok) {
290         HILOG_ERROR_I18N("JSRelativeTimeFormatAddon::FormatToParts: Get parameter info failed");
291         return JSUtils::CreateEmptyArray(env);
292     }
293     if (argc < argsCount) {
294         HILOG_ERROR_I18N("JSRelativeTimeFormatAddon::FormatToParts: parameter count less then required.");
295         return JSUtils::CreateEmptyArray(env);
296     }
297     napi_valuetype valueType = napi_valuetype::napi_undefined;
298     status = napi_typeof(env, argv[0], &valueType);
299     if (status != napi_ok) {
300         HILOG_ERROR_I18N("JSRelativeTimeFormatAddon::FormatToParts: Get param type failed.");
301         return JSUtils::CreateEmptyArray(env);
302     }
303     int32_t code = 0;
304     double number = JSUtils::GetDoubleFromNapiValue(env, argv[0], valueType, code);
305     if (code != 0) {
306         HILOG_ERROR_I18N("JSRelativeTimeFormatAddon::FormatToParts: Get parameter number failed");
307         return JSUtils::CreateEmptyArray(env);
308     }
309     std::string unit = JSUtils::GetString(env, argv[argsCount - 1], code);
310     if (code != 0) {
311         HILOG_ERROR_I18N("JSRelativeTimeFormatAddon::FormatToParts: Get second parameter failed");
312         return JSUtils::CreateEmptyArray(env);
313     }
314     return GetFormatToPartsInner(env, number, unit, thisVar);
315 }
316 
GetFormatToPartsInner(napi_env env,double number,const std::string & unit,napi_value & thisVar)317 napi_value JSRelativeTimeFormatAddon::GetFormatToPartsInner(napi_env env, double number,
318     const std::string &unit, napi_value &thisVar)
319 {
320     JSRelativeTimeFormatAddon *obj = nullptr;
321     napi_status status = napi_unwrap(env, thisVar, reinterpret_cast<void **>(&obj));
322     if (status != napi_ok || !obj || !obj->relativetimefmt_) {
323         HILOG_ERROR_I18N("GetFormatToPartsInner: Get NumberFormat object failed");
324         return JSUtils::CreateEmptyArray(env);
325     }
326     std::vector<std::vector<std::string>> relativeTimeParts;
327     obj->relativetimefmt_->FormatToParts(number, unit, relativeTimeParts);
328     return SetNumberFormatParts(env, relativeTimeParts);
329 }
330 
SupportedLocalesOf(napi_env env,napi_callback_info info)331 napi_value JSRelativeTimeFormatAddon::SupportedLocalesOf(napi_env env, napi_callback_info info)
332 {
333     size_t argc = 2;
334     napi_value argv[2] = { nullptr };
335     napi_value thisVar = nullptr;
336     napi_status status = napi_get_cb_info(env, info, &argc, argv, &thisVar, nullptr);
337     if (status != napi_ok) {
338         HILOG_ERROR_I18N("JSRelativeTimeFormatAddon::SupportedLocalesOf: Get parameter info failed");
339         return JSUtils::CreateEmptyArray(env);
340     }
341     std::vector<std::string> localeTags;
342     if (argc < 1) {
343         return JSUtils::CreateArray(env, localeTags);
344     }
345     int32_t code = 0;
346     std::vector<std::string> localeArray = JSUtils::GetLocaleArray(env, argv[0], code);
347     localeTags.assign(localeArray.begin(), localeArray.end());
348     std::map<std::string, std::string> map = {};
349     if (argc > 1) {
350         JSUtils::GetOptionValue(env, argv[1], "localeMatcher", map);
351         CheckOptionValue(env, map, "localeMatcher");
352     }
353     I18nErrorCode i18nStatus = I18nErrorCode::SUCCESS;
354     std::vector<std::string> resultLocales = RelativeTimeFormat::SupportedLocalesOf(localeTags, map, i18nStatus);
355     if (i18nStatus == I18nErrorCode::INVALID_LOCALE_TAG) {
356         HILOG_ERROR_I18N("JSRelativeTimeFormatAddon::SupportedLocalesOf: SupportedLocalesOf status fail");
357         ErrorUtil::NapiThrowUndefined(env, "invalid locale");
358         return nullptr;
359     } else if (i18nStatus != I18nErrorCode::SUCCESS) {
360         ErrorUtil::NapiThrowUndefined(env, "getStringOption failed");
361         return nullptr;
362     }
363     return JSUtils::CreateArray(env, resultLocales);
364 }
365 
SetNumberFormatParts(napi_env env,const std::vector<std::vector<std::string>> & numberParts)366 napi_value JSRelativeTimeFormatAddon::SetNumberFormatParts(napi_env env,
367     const std::vector<std::vector<std::string>> &numberParts)
368 {
369     napi_value result = nullptr;
370     napi_status status = napi_create_array_with_length(env, numberParts.size(), &result);
371     if (status != napi_ok) {
372         HILOG_ERROR_I18N("SetNumberFormatParts: Failed to create array");
373         return JSUtils::CreateEmptyArray(env);
374     }
375     for (size_t i = 0; i < numberParts.size(); i++) {
376         if (numberParts[i].size() < 2) { // 2 is minimum vector's size
377             HILOG_ERROR_I18N("SetNumberFormatParts: numberParts's item in %{public}zu lack key&value pair", i);
378             return JSUtils::CreateEmptyArray(env);
379         }
380         napi_value value = JSUtils::CreateString(env, numberParts[i][1].c_str());
381         if (value == nullptr) {
382             HILOG_ERROR_I18N("Failed to create string item numberParts[i][1].");
383             return JSUtils::CreateEmptyArray(env);
384         }
385         napi_value type = JSUtils::CreateString(env, numberParts[i][0].c_str());
386         if (type == nullptr) {
387             HILOG_ERROR_I18N("Failed to create string item numberParts[i][0].");
388             return JSUtils::CreateEmptyArray(env);
389         }
390         napi_value unit = nullptr;
391         if (numberParts[i].size() > 2) { // 2 is vector's size which not contains unit
392             unit = JSUtils::CreateString(env, numberParts[i][2].c_str());
393             if (unit == nullptr) {
394                 HILOG_ERROR_I18N("Failed to create string item numberParts[i][2].");
395                 return JSUtils::CreateEmptyArray(env);
396             }
397         }
398         std::unordered_map<std::string, napi_value> propertys {
399             { "type", type },
400             { "value", value },
401             { "unit", unit }
402         };
403         std::vector<std::string> keyVect = { "type", "value", "unit" };
404         int32_t code = 0;
405         napi_value formatInfo = JSUtils::CreateArrayItem(env, propertys, code, keyVect);
406         if (code != 0) {
407             return JSUtils::CreateEmptyArray(env);
408         }
409         status = napi_set_element(env, result, i, formatInfo);
410         if (status != napi_ok) {
411             HILOG_ERROR_I18N("Failed to set array item");
412             return JSUtils::CreateEmptyArray(env);
413         }
414     }
415     return result;
416 }
417 
418 } // namespace I18n
419 } // namespace Global
420 } // namespace OHOS