1 /*
2  * Copyright (c) 2021-2024 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 "builtins_date_time_format.h"
17 
18 #include "ecmascript/intl/locale_helper.h"
19 #include "ecmascript/global_env.h"
20 #include "ecmascript/js_date.h"
21 #include "ecmascript/js_date_time_format.h"
22 #include "ecmascript/js_function.h"
23 #include "ecmascript/js_intl.h"
24 
25 namespace panda::ecmascript::builtins {
26 // 13.2.1 Intl.DateTimeFormat ( [ locales [ , options ] ] )
DateTimeFormatConstructor(EcmaRuntimeCallInfo *argv)27 JSTaggedValue BuiltinsDateTimeFormat::DateTimeFormatConstructor(EcmaRuntimeCallInfo *argv)
28 {
29     JSThread *thread = argv->GetThread();
30     BUILTINS_API_TRACE(thread, DateTimeFormat, Constructor);
31     [[maybe_unused]] EcmaHandleScope scope(thread);
32     EcmaVM *ecmaVm = thread->GetEcmaVM();
33     JSHandle<GlobalEnv> env = ecmaVm->GetGlobalEnv();
34     ObjectFactory *factory = ecmaVm->GetFactory();
35 
36     // 1. If NewTarget is undefined, let newTarget be the active function object, else let newTarget be NewTarget.
37     JSHandle<JSTaggedValue> constructor = GetConstructor(argv);
38     JSHandle<JSTaggedValue> newTarget = GetNewTarget(argv);
39     if (newTarget->IsUndefined()) {
40         newTarget = constructor;
41     }
42 
43     // 2. Let dateTimeFormat be ? OrdinaryCreateFromConstructor(newTarget, "%DateTimeFormatPrototype%", «
44     //    [[InitializedDateTimeFormat]], [[Locale]], [[Calendar]], [[NumberingSystem]], [[TimeZone]], [[Weekday]],
45     //    [[Era]], [[Year]], [[Month]], [[Day]], [[Hour]], [[Minute]], [[Second]], [[TimeZoneName]], [[HourCycle]],
46     //    [[Pattern]], [[BoundFormat]] »).
47     JSHandle<JSObject> newObject = factory->NewJSObjectByConstructor(JSHandle<JSFunction>(constructor), newTarget);
48     RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
49     JSHandle<JSDateTimeFormat> dateTimeFormat = JSHandle<JSDateTimeFormat>::Cast(newObject);
50 
51     // 3. Perform ? InitializeDateTimeFormat(dateTimeFormat, locales, options).
52     JSHandle<JSTaggedValue> locales = GetCallArg(argv, 0);
53     JSHandle<JSTaggedValue> options = GetCallArg(argv, 1);
54     JSHandle<JSDateTimeFormat> dtf =
55         JSDateTimeFormat::InitializeDateTimeFormat(thread, dateTimeFormat, locales, options);
56     RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
57 
58     // 4. Let this be the this value.
59     JSHandle<JSTaggedValue> thisValue = GetThis(argv);
60 
61     // 5. If NewTarget is undefined and Type(this) is Object and ? InstanceofOperator(this, %DateTimeFormat%) is true,
62     //    then
63     //    a. Perform ? DefinePropertyOrThrow(this, %Intl%.[[FallbackSymbol]], PropertyDescriptor{ [[Value]]:
64     //       dateTimeFormat, [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: false }).
65     //    b. Return this.
66     if (GetNewTarget(argv)->IsUndefined() && thisValue->IsJSObject()) {
67         bool isInstanceOf = JSFunction::OrdinaryHasInstance(thread, env->GetDateTimeFormatFunction(), thisValue);
68         RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
69         if (isInstanceOf) {
70             PropertyDescriptor descriptor(thread, JSHandle<JSTaggedValue>::Cast(dtf), false, false, false);
71             JSHandle<JSTaggedValue> key(thread, JSHandle<JSIntl>::Cast(env->GetIntlFunction())->GetFallbackSymbol());
72             JSTaggedValue::DefinePropertyOrThrow(thread, thisValue, key, descriptor);
73             RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
74             return thisValue.GetTaggedValue();
75         }
76     }
77 
78     // 6. Return dateTimeFormat.
79     return dtf.GetTaggedValue();
80 }
81 
82 // 13.3.2 Intl.DateTimeFormat.supportedLocalesOf ( locales [ , options ] )
SupportedLocalesOf(EcmaRuntimeCallInfo *argv)83 JSTaggedValue BuiltinsDateTimeFormat::SupportedLocalesOf(EcmaRuntimeCallInfo *argv)
84 {
85     JSThread *thread = argv->GetThread();
86     BUILTINS_API_TRACE(thread, DateTimeFormat, SupportedLocalesOf);
87     [[maybe_unused]] EcmaHandleScope scope(thread);
88     // 1. Let availableLocales be %DateTimeFormat%.[[AvailableLocales]].
89     JSHandle<TaggedArray> availableLocales = JSDateTimeFormat::GainAvailableLocales(thread);
90 
91     // 2. Let requestedLocales be ? CanonicalizeLocaleList(locales).
92     JSHandle<JSTaggedValue> locales = GetCallArg(argv, 0);
93     JSHandle<TaggedArray> requestedLocales = intl::LocaleHelper::CanonicalizeLocaleList(thread, locales);
94     RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
95 
96     // 3. Return ? SupportedLocales(availableLocales, requestedLocales, options).
97     JSHandle<JSTaggedValue> options = GetCallArg(argv, 1);
98     JSHandle<JSArray> result = JSLocale::SupportedLocales(thread, availableLocales, requestedLocales, options);
99     RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
100     return result.GetTaggedValue();
101 }
102 
103 // 13.4.3 get Intl.DateTimeFormat.prototype.format
Format(EcmaRuntimeCallInfo *argv)104 JSTaggedValue BuiltinsDateTimeFormat::Format(EcmaRuntimeCallInfo *argv)
105 {
106     JSThread *thread = argv->GetThread();
107     BUILTINS_API_TRACE(thread, DateTimeFormat, Format);
108     [[maybe_unused]] EcmaHandleScope scope(thread);
109 
110     // 1. Let dtf be this value.
111     JSHandle<JSTaggedValue> thisValue = GetThis(argv);
112 
113     // 2. If Type(dtf) is not Object, throw a TypeError exception.
114     if (!thisValue->IsJSObject()) {
115         THROW_TYPE_ERROR_AND_RETURN(thread, "dtf is not object", JSTaggedValue::Exception());
116     }
117 
118     // 3. Let dtf be ? UnwrapDateTimeFormat(dtf).
119     JSHandle<JSTaggedValue> dtfValue = JSDateTimeFormat::UnwrapDateTimeFormat(thread, thisValue);
120     RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
121     if (dtfValue->IsUndefined()) {
122         THROW_TYPE_ERROR_AND_RETURN(thread, "dtfValue is not object", JSTaggedValue::Exception());
123     }
124 
125     // 4. If dtf.[[BoundFormat]] is undefined, then
126     //    a. Let F be a new built-in function object as defined in DateTime Format Functions (13.1.5).
127     //    b. Set F.[[DateTimeFormat]] to dtf.
128     //    c. Set dtf.[[BoundFormat]] to F.
129     // 5. Return dtf.[[BoundFormat]].
130     JSHandle<JSDateTimeFormat> dtf = JSHandle<JSDateTimeFormat>::Cast(dtfValue);
131     JSHandle<JSTaggedValue> boundFormat(thread, dtf->GetBoundFormat());
132     if (boundFormat->IsUndefined()) {
133         ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
134         JSHandle<JSIntlBoundFunction> intlBoundFunc = factory->NewJSIntlBoundFunction(
135             MethodIndex::BUILTINS_DATE_TIME_FORMAT_ANONYMOUS_DATE_TIME_FORMAT);
136         intlBoundFunc->SetDateTimeFormat(thread, dtf);
137         dtf->SetBoundFormat(thread, intlBoundFunc);
138     }
139     return dtf->GetBoundFormat();
140 }
141 
142 // 13.1.5 DateTime Format Functions
AnonymousDateTimeFormat(EcmaRuntimeCallInfo *argv)143 JSTaggedValue BuiltinsDateTimeFormat::AnonymousDateTimeFormat(EcmaRuntimeCallInfo *argv)
144 {
145     // A DateTime format function is an anonymous built-in function that has a [[DateTimeFormat]] internal slot.
146     // When a DateTime format function F is called with optional argument date, the following steps are taken:
147     JSThread *thread = argv->GetThread();
148     BUILTINS_API_TRACE(thread, DateTimeFormat, AnonymousDateTimeFormat);
149     [[maybe_unused]] EcmaHandleScope scope(thread);
150     JSHandle<JSIntlBoundFunction> intlBoundFunc = JSHandle<JSIntlBoundFunction>::Cast(GetConstructor(argv));
151 
152     // 1. Let dtf be F.[[DateTimeFormat]].
153     JSHandle<JSTaggedValue> dtf(thread, intlBoundFunc->GetDateTimeFormat());
154 
155     // 2. Assert: Type(dtf) is Object and dtf has an [[InitializedDateTimeFormat]] internal slot.
156     ASSERT_PRINT(dtf->IsJSObject() && dtf->IsJSDateTimeFormat(), "dtf is not object or JSDateTimeFormat");
157 
158     // 3. If date is not provided or is undefined, then
159     //    a. Let x be Call(%Date_now%, undefined).
160     // 4. Else,
161     //    a. Let x be ? ToNumber(date).
162     JSHandle<JSTaggedValue> date = GetCallArg(argv, 0);
163     double x = 0.0;
164     if (date->IsUndefined()) {
165         x = JSDate::Now().GetNumber();
166     } else {
167         JSTaggedNumber xNumber = JSTaggedValue::ToNumber(thread, date);
168         RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
169         x = xNumber.GetNumber();
170     }
171 
172     // 5. Return ? FormatDateTime(dtf, x).
173     JSHandle<EcmaString> result = JSDateTimeFormat::FormatDateTime(thread, JSHandle<JSDateTimeFormat>::Cast(dtf), x);
174     RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
175     return result.GetTaggedValue();
176 }
177 
178 // 13.4.4 Intl.DateTimeFormat.prototype.formatToParts ( date )
FormatToParts(EcmaRuntimeCallInfo *argv)179 JSTaggedValue BuiltinsDateTimeFormat::FormatToParts(EcmaRuntimeCallInfo *argv)
180 {
181     JSThread *thread = argv->GetThread();
182     BUILTINS_API_TRACE(thread, DateTimeFormat, FormatToParts);
183     [[maybe_unused]] EcmaHandleScope scope(thread);
184     // 1. Let dtf be this value.
185     JSHandle<JSTaggedValue> dtf = GetThis(argv);
186     // 2. Perform ? RequireInternalSlot(dtf, [[InitializedDateTimeFormat]]).
187     if (!dtf->IsJSDateTimeFormat()) {
188         THROW_TYPE_ERROR_AND_RETURN(thread, "is not JSDateTimeFormat", JSTaggedValue::Exception());
189     }
190 
191     // 3. If date is undefined, then
192     //    a. Let x be Call(%Date_now%, undefined).
193     // 4. Else,
194     //    a. Let x be ? ToNumber(date).
195     JSHandle<JSTaggedValue> date = GetCallArg(argv, 0);
196     double x = 0.0;
197     if (date->IsUndefined()) {
198         x = JSDate::Now().GetNumber();
199     } else {
200         JSTaggedNumber xNumber = JSTaggedValue::ToNumber(thread, date);
201         RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
202         x = xNumber.GetNumber();
203     }
204 
205     double xValue = JSDate::TimeClip(x);
206     if (std::isnan(xValue)) {
207         THROW_RANGE_ERROR_AND_RETURN(thread, "Invalid time value", JSTaggedValue::Exception());
208     }
209 
210     // 5. Return ? FormatDateTimeToParts(dtf, x).
211     JSHandle<JSArray> result =
212         JSDateTimeFormat::FormatDateTimeToParts(thread, JSHandle<JSDateTimeFormat>::Cast(dtf), xValue);
213     RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
214     return result.GetTaggedValue();
215 }
216 
217 // 13.4.5 Intl.DateTimeFormat.prototype.resolvedOptions ()
ResolvedOptions(EcmaRuntimeCallInfo *argv)218 JSTaggedValue BuiltinsDateTimeFormat::ResolvedOptions(EcmaRuntimeCallInfo *argv)
219 {
220     JSThread *thread = argv->GetThread();
221     BUILTINS_API_TRACE(thread, DateTimeFormat, ResolvedOptions);
222     [[maybe_unused]] EcmaHandleScope scope(thread);
223     // 1. Let dtf be this value.
224     JSHandle<JSTaggedValue> thisValue = GetThis(argv);
225     // 2. If Type(dtf) is not Object, throw a TypeError exception.
226     if (!thisValue->IsJSObject()) {
227         THROW_TYPE_ERROR_AND_RETURN(thread, "this is not object", JSTaggedValue::Exception());
228     }
229     // 3. Let dtf be ? UnwrapDateTimeFormat(dtf).
230     thisValue = JSDateTimeFormat::UnwrapDateTimeFormat(thread, thisValue);
231     RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
232 
233     // Perform ? RequireInternalSlot(dtf, [[InitializedDateTimeFormat]]).
234     if (!thisValue->IsJSDateTimeFormat()) {
235         THROW_TYPE_ERROR_AND_RETURN(thread, "dtf is not JSDateTimeFormat", JSTaggedValue::Exception());
236     }
237 
238     auto ecmaVm = thread->GetEcmaVM();
239     JSHandle<GlobalEnv> env = ecmaVm->GetGlobalEnv();
240     ObjectFactory *factory = ecmaVm->GetFactory();
241     JSHandle<JSFunction> ctor(env->GetObjectFunction());
242     JSHandle<JSObject> options(factory->NewJSObjectByConstructor(ctor));
243     JSDateTimeFormat::ResolvedOptions(thread, JSHandle<JSDateTimeFormat>::Cast(thisValue), options);
244     // 6. Return options.
245     return options.GetTaggedValue();
246 }
247 
248 // Intl.DateTimeFormat.prototype.formatRange
FormatRange(EcmaRuntimeCallInfo *argv)249 JSTaggedValue BuiltinsDateTimeFormat::FormatRange(EcmaRuntimeCallInfo *argv)
250 {
251     JSThread *thread = argv->GetThread();
252     BUILTINS_API_TRACE(thread, DateTimeFormat, FormatRange);
253     [[maybe_unused]] EcmaHandleScope scope(thread);
254 
255     // 1. Let dtf be this value.
256     JSHandle<JSTaggedValue> thisValue = GetThis(argv);
257     // 2. If Type(dtf) is not Object, throw a TypeError exception.
258     if (!thisValue->IsJSObject()) {
259         THROW_TYPE_ERROR_AND_RETURN(thread, "this is not object", JSTaggedValue::Exception());
260     }
261 
262     // 3. If dtf does not have an [[InitializedDateTimeFormat]] internal slot, throw a TypeError exception.
263     if (!thisValue->IsJSDateTimeFormat()) {
264         THROW_TYPE_ERROR_AND_RETURN(thread, "this is not JSDateTimeFormat", JSTaggedValue::Exception());
265     }
266 
267     // 4. If startDate is undefined or endDate is undefined, throw a TypeError exception.
268     JSHandle<JSTaggedValue> startDate = GetCallArg(argv, 0);
269     JSHandle<JSTaggedValue> endDate = GetCallArg(argv, 1);
270     if (startDate->IsUndefined() || endDate->IsUndefined()) {
271         THROW_TYPE_ERROR_AND_RETURN(thread, "startDate or endDate is undefined", JSTaggedValue::Exception());
272     }
273 
274     // 5. Let x be ? ToNumber(startDate).
275     JSTaggedNumber valueX = JSTaggedValue::ToNumber(thread, startDate);
276     RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
277     double x = valueX.GetNumber();
278 
279     // 6. Let y be ? ToNumber(endDate).
280     JSTaggedNumber valueY = JSTaggedValue::ToNumber(thread, endDate);
281     RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
282     double y = valueY.GetNumber();
283 
284     // 8. Return ? FormatDateTimeRange(dtf, x, y)
285     JSHandle<JSDateTimeFormat> dtf = JSHandle<JSDateTimeFormat>::Cast(thisValue);
286     JSHandle<EcmaString> result = JSDateTimeFormat::NormDateTimeRange(thread, dtf, x, y);
287     RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
288     return result.GetTaggedValue();
289 }
290 
291 // Intl.DateTimeFormat.prototype.formatRangeToParts
FormatRangeToParts(EcmaRuntimeCallInfo *argv)292 JSTaggedValue BuiltinsDateTimeFormat::FormatRangeToParts(EcmaRuntimeCallInfo *argv)
293 {
294     JSThread *thread = argv->GetThread();
295     BUILTINS_API_TRACE(thread, DateTimeFormat, FormatRangeToParts);
296     [[maybe_unused]] EcmaHandleScope scope(thread);
297     // 1. Let dtf be this value.
298     JSHandle<JSTaggedValue> thisValue = GetThis(argv);
299     // 2. If Type(dtf) is not Object, throw a TypeError exception.
300     if (!thisValue->IsJSObject()) {
301         THROW_TYPE_ERROR_AND_RETURN(thread, "this is not object", JSTaggedValue::Exception());
302     }
303 
304     // 3. If dtf does not have an [[InitializedDateTimeFormat]] internal slot,
305     //    throw a TypeError exception.
306     if (!thisValue->IsJSDateTimeFormat()) {
307         THROW_TYPE_ERROR_AND_RETURN(thread, "this is not JSDateTimeFormat", JSTaggedValue::Exception());
308     }
309 
310     // 4. If startDate is undefined or endDate is undefined, throw a TypeError exception.
311     JSHandle<JSTaggedValue> startDate = GetCallArg(argv, 0);
312     JSHandle<JSTaggedValue> endDate = GetCallArg(argv, 1);
313     if (startDate->IsUndefined() || endDate->IsUndefined()) {
314         THROW_TYPE_ERROR_AND_RETURN(thread, "startDate or endDate is undefined", JSTaggedValue::Exception());
315     }
316 
317     // 5. Let x be ? ToNumber(startDate).
318     JSTaggedNumber valueX = JSTaggedValue::ToNumber(thread, startDate);
319     RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
320     double x = valueX.GetNumber();
321 
322     // 6. Let y be ? ToNumber(endDate).
323     JSTaggedNumber valueY = JSTaggedValue::ToNumber(thread, endDate);
324     RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
325     double y = valueY.GetNumber();
326 
327     // 8. Return ? FormatDateTimeRangeToParts(dtf, x, y)
328     JSHandle<JSDateTimeFormat> dtf = JSHandle<JSDateTimeFormat>::Cast(thisValue);
329     JSHandle<JSArray> result = JSDateTimeFormat::NormDateTimeRangeToParts(thread, dtf, x, y);
330     RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
331     return result.GetTaggedValue();
332 }
333 }  // namespace panda::ecmascript::builtins