1 /*
2 * Copyright (c) 2021 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 "js_date.h"
17 #include <ctime>
18 #include <regex>
19 #include <sys/time.h>
20 #include "base/builtins_base.h"
21
22 namespace panda::ecmascript {
23 using NumberHelper = base::NumberHelper;
24 bool DateUtils::isCached_ = false;
25 int32_t DateUtils::preSumDays_ = 0;
26 int32_t DateUtils::preDays_ = 0;
27 int32_t DateUtils::preYear_ = 0;
TransferTimeToDate(int64_t timeMs,std::array<int64_t,DATE_LENGTH> * date)28 void DateUtils::TransferTimeToDate(int64_t timeMs, std::array<int64_t, DATE_LENGTH> *date)
29 {
30 (*date)[HOUR] = Mod(timeMs, MS_PER_DAY); // ms from hour, minutes, second, ms
31 (*date)[DAYS] = (timeMs - (*date)[HOUR]) / MS_PER_DAY; // days from year, month, day
32 (*date)[MS] = (*date)[HOUR] % MS_PER_SECOND; // ms
33 (*date)[HOUR] = ((*date)[HOUR] - (*date)[MS]) / MS_PER_SECOND; // s from hour, minutes, second
34 (*date)[SEC] = (*date)[HOUR] % SEC_PER_MINUTE; // second
35 (*date)[HOUR] = ((*date)[HOUR] - (*date)[SEC]) / SEC_PER_MINUTE; // min from hour, minutes
36 (*date)[MIN] = (*date)[HOUR] % SEC_PER_MINUTE; // min
37 (*date)[HOUR] = ((*date)[HOUR] - (*date)[MIN]) / SEC_PER_MINUTE; // hour
38 (*date)[WEEKDAY] = Mod(((*date)[DAYS] + LEAP_NUMBER[0]), DAY_PER_WEEK); // weekday
39 (*date)[YEAR] = GetYearFromDays(&((*date)[DAYS])); // year
40 }
41 // static
IsLeap(int64_t year)42 bool DateUtils::IsLeap(int64_t year)
43 {
44 return year % LEAP_NUMBER[0] == 0 && (year % LEAP_NUMBER[1] != 0 || year % LEAP_NUMBER[2] == 0); // 2: means index
45 }
46
47 // static
GetDaysInYear(int64_t year)48 int64_t DateUtils::GetDaysInYear(int64_t year)
49 {
50 int64_t number = IsLeap(year) ? (DAYS_IN_YEAR + 1) : DAYS_IN_YEAR;
51 return number;
52 }
53
54 // static
GetDaysFromYear(int64_t year)55 int64_t DateUtils::GetDaysFromYear(int64_t year)
56 {
57 return DAYS_IN_YEAR * (year - YEAR_NUMBER[0]) + FloorDiv(year - YEAR_NUMBER[1], LEAP_NUMBER[0]) -
58 FloorDiv(year - YEAR_NUMBER[2], LEAP_NUMBER[1]) + // 2: year index
59 FloorDiv(year - YEAR_NUMBER[3], LEAP_NUMBER[2]); // 3, 2: year index
60 }
61
62 // static
FloorDiv(int64_t a,int64_t b)63 int64_t DateUtils::FloorDiv(int64_t a, int64_t b)
64 {
65 ASSERT(b != 0);
66 int64_t m = a % b;
67 int64_t res = m < 0 ? ((a - m - b) / b) : ((a - m) / b);
68 return res;
69 }
70
71 // static
GetYearFromDays(int64_t * days)72 int64_t DateUtils::GetYearFromDays(int64_t *days)
73 {
74 if (isCached_ && preSumDays_ == *days) {
75 *days = preDays_;
76 return preYear_;
77 }
78 int64_t realDay;
79 int64_t dayTemp = 0;
80 int64_t d = *days;
81 preSumDays_ = d;
82 int64_t year = FloorDiv(d * APPROXIMATION_NUMBER[0], APPROXIMATION_NUMBER[1]) + YEAR_NUMBER[0];
83 realDay = d - GetDaysFromYear(year);
84 while (realDay != 0) {
85 if (realDay < 0) {
86 year--;
87 } else {
88 dayTemp = GetDaysInYear(year);
89 if (realDay < dayTemp) {
90 break;
91 }
92 year++;
93 }
94 realDay = d - GetDaysFromYear(year);
95 }
96 *days = realDay;
97 preDays_ = realDay;
98 preYear_ = year;
99 isCached_ = true;
100 return year;
101 }
102
103 // static
Mod(int64_t a,int b)104 int64_t DateUtils::Mod(int64_t a, int b)
105 {
106 ASSERT(b != 0);
107 int64_t m = a % b;
108 int64_t res = m < 0 ? (m + b) : m;
109 return res;
110 }
111
112 // static
113 // 20.4.1.11
MakeTime(double hour,double min,double sec,double ms)114 double JSDate::MakeTime(double hour, double min, double sec, double ms)
115 {
116 if (std::isfinite(hour) && std::isfinite(min) && std::isfinite(sec) && std::isfinite(ms)) {
117 double hourInteger = NumberHelper::TruncateDouble(hour);
118 double minInteger = NumberHelper::TruncateDouble(min);
119 double secInteger = NumberHelper::TruncateDouble(sec);
120 double msInteger = NumberHelper::TruncateDouble(ms);
121 return hourInteger * MS_PER_HOUR + minInteger * MS_PER_MINUTE + secInteger * MS_PER_SECOND + msInteger;
122 }
123 return base::NAN_VALUE;
124 }
125
126 // static
127 // 20.4.1.12
MakeDay(double year,double month,double date)128 double JSDate::MakeDay(double year, double month, double date)
129 {
130 if (std::isfinite(year) && std::isfinite(month) && std::isfinite(date)) {
131 double yearInteger = NumberHelper::TruncateDouble(year);
132 double monthInteger = NumberHelper::TruncateDouble(month);
133 int64_t y = static_cast<int64_t>(yearInteger) + static_cast<int64_t>(monthInteger / MOUTH_PER_YEAR);
134 int64_t m = static_cast<int64_t>(monthInteger) % MOUTH_PER_YEAR;
135 if (m < 0) {
136 m += MOUTH_PER_YEAR;
137 y -= 1;
138 }
139
140 int64_t days = DateUtils::GetDaysFromYear(y);
141 int index = DateUtils::IsLeap(year) ? 1 : 0;
142 days += DAYS_FROM_MONTH[index][m];
143 return static_cast<double>(days - 1) + NumberHelper::TruncateDouble(date);
144 }
145 return base::NAN_VALUE;
146 }
147
148 // static
149 // 20.4.1.13
MakeDate(double day,double time)150 double JSDate::MakeDate(double day, double time)
151 {
152 if (std::isfinite(day) && std::isfinite(time)) {
153 return time + day * MS_PER_DAY;
154 }
155 return base::NAN_VALUE;
156 }
157
158 // static
159 // 20.4.1.14
TimeClip(double time)160 double JSDate::TimeClip(double time)
161 {
162 if (-MAX_TIME_IN_MS <= time && time <= MAX_TIME_IN_MS) {
163 return NumberHelper::TruncateDouble(time);
164 }
165 return base::NAN_VALUE;
166 }
167
168 // 20.4.1.8
LocalTime(double timeMs) const169 double JSDate::LocalTime(double timeMs) const
170 {
171 return timeMs + GetLocalOffsetFromOS(timeMs, true);
172 }
173
174 // 20.4.1.9
UTCTime(double timeMs) const175 double JSDate::UTCTime(double timeMs) const
176 {
177 return timeMs - GetLocalOffsetFromOS(timeMs, false);
178 }
179
180 // static
GetSignedNumFromString(const CString & str,int len,int * index)181 int JSDate::GetSignedNumFromString(const CString &str, int len, int *index)
182 {
183 int res = 0;
184 GetNumFromString(str, len, index, &res);
185 if (str.at(0) == NEG) {
186 return -res;
187 }
188 return res;
189 }
190
191 // static
GetNumFromString(const CString & str,int len,int * index,int * num)192 bool JSDate::GetNumFromString(const CString &str, int len, int *index, int *num)
193 {
194 int indexStr = *index;
195 char oneByte = 0;
196 while (indexStr < len) {
197 oneByte = str.at(indexStr);
198 if (oneByte >= '0' && oneByte <= '9') {
199 break;
200 }
201 indexStr++;
202 }
203 if (indexStr >= len) {
204 return false;
205 }
206 int value = 0;
207 while (indexStr < len) {
208 oneByte = str.at(indexStr);
209 int val = oneByte - '0';
210 if (val >= 0 && val <= NUM_NINE) {
211 value = value * TEN + val;
212 indexStr++;
213 } else {
214 break;
215 }
216 }
217 *num = value;
218 *index = indexStr;
219 return true;
220 }
221
222 // 20.4.1.7
GetLocalOffsetInMin(const JSThread * thread,int64_t timeMs,bool isLocal)223 int64_t JSDate::GetLocalOffsetInMin(const JSThread *thread, int64_t timeMs, bool isLocal)
224 {
225 if (!isLocal) {
226 return 0;
227 }
228 double localOffset = this->GetLocalOffset().GetDouble();
229 if (localOffset == MAX_DOUBLE) {
230 localOffset = static_cast<double>(GetLocalOffsetFromOS(timeMs, isLocal));
231 SetLocalOffset(thread, JSTaggedValue(localOffset));
232 }
233 return localOffset;
234 }
235
236 // static
LocalParseStringToMs(const CString & str)237 JSTaggedValue JSDate::LocalParseStringToMs(const CString &str)
238 {
239 int year = 0;
240 int month = 0;
241 int date = 1;
242 int hours = 0;
243 int minutes = 0;
244 int seconds = 0;
245 int ms = 0;
246 int index = 0;
247 int len = str.length();
248 bool isLocal = false;
249 CString::size_type indexGmt;
250 CString::size_type indexPlus = CString::npos;
251 std::array<CString, MOUTH_PER_YEAR> monthName = {
252 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
253 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
254 };
255 int localTime = 0;
256 int localHours = 0;
257 int localMinutes = 0;
258 int64_t localMs = 0;
259 CString::size_type localSpace = str.find(' ', index);
260 CString strMonth = str.substr(localSpace + 1, LENGTH_MONTH_NAME);
261 for (int i = 0; i < MOUTH_PER_YEAR; i++) {
262 if (strMonth == monthName[i]) {
263 month = i;
264 break;
265 }
266 }
267 index += (LENGTH_MONTH_NAME + 1);
268 GetNumFromString(str, len, &index, &date);
269 GetNumFromString(str, len, &index, &year);
270 indexGmt = str.find("GMT", index);
271 if (indexGmt == CString::npos) {
272 GetNumFromString(str, len, &index, &hours);
273 GetNumFromString(str, len, &index, &minutes);
274 GetNumFromString(str, len, &index, &seconds);
275 isLocal = true;
276 localMs -= (GetLocalOffsetFromOS(localMs, true) * MS_PER_MINUTE);
277 } else {
278 indexPlus = str.find(PLUS, indexGmt);
279 int indexLocal = static_cast<int>(indexGmt);
280 GetNumFromString(str, indexGmt, &index, &hours);
281 GetNumFromString(str, indexGmt, &index, &minutes);
282 GetNumFromString(str, indexGmt, &index, &seconds);
283 GetNumFromString(str, len, &indexLocal, &localTime);
284 localHours = localTime / HUNDRED;
285 localMinutes = localTime % HUNDRED;
286 localMs = static_cast<int64_t>(MakeTime(localHours, localMinutes, 0, 0));
287 if (indexPlus != CString::npos) {
288 localMs = -localMs;
289 }
290 }
291 double day = MakeDay(year, month, date);
292 double time = MakeTime(hours, minutes, seconds, ms);
293 double timeValue = TimeClip(MakeDate(day, time));
294 if (std::isnan(timeValue)) {
295 return JSTaggedValue(timeValue);
296 }
297 if (isLocal && timeValue < CHINA_1901_MS && (-localMs / MS_PER_MINUTE) == CHINA_AFTER_1901_MIN) {
298 timeValue += static_cast<double>(localMs - CHINA_BEFORE_1901_MS);
299 } else {
300 timeValue += localMs;
301 }
302 return JSTaggedValue(timeValue);
303 }
304
305 // static
UtcParseStringToMs(const CString & str)306 JSTaggedValue JSDate::UtcParseStringToMs(const CString &str)
307 {
308 int year = 0;
309 int month = 0;
310 int date = 1;
311 int hours = 0;
312 int minutes = 0;
313 int seconds = 0;
314 int ms = 0;
315 int index = 0;
316 int len = str.length();
317 CString::size_type indexGmt;
318 CString::size_type indexPlus = CString::npos;
319 int localTime = 0;
320 int localHours = 0;
321 int localMinutes = 0;
322 int64_t localMs = 0;
323 bool isLocal = false;
324 std::array<CString, MOUTH_PER_YEAR> monthName = {
325 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
326 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
327 };
328 GetNumFromString(str, len, &index, &date);
329 CString strMonth = str.substr(index + 1, LENGTH_MONTH_NAME);
330 for (int i = 0; i < MOUTH_PER_YEAR; i++) {
331 if (strMonth == monthName[i]) {
332 month = i;
333 break;
334 }
335 }
336 index += (LENGTH_MONTH_NAME + 1);
337 GetNumFromString(str, len, &index, &year);
338 indexGmt = str.find("GMT", index);
339 if (indexGmt == CString::npos) {
340 GetNumFromString(str, len, &index, &hours);
341 GetNumFromString(str, len, &index, &minutes);
342 GetNumFromString(str, len, &index, &seconds);
343 isLocal = true;
344 localMs -= (GetLocalOffsetFromOS(localMs, true) * MS_PER_MINUTE);
345 } else {
346 indexPlus = str.find(PLUS, indexGmt);
347 int indexLocal = static_cast<int>(indexGmt);
348 GetNumFromString(str, indexGmt, &index, &hours);
349 GetNumFromString(str, indexGmt, &index, &minutes);
350 GetNumFromString(str, indexGmt, &index, &seconds);
351 GetNumFromString(str, len, &indexLocal, &localTime);
352 localHours = localTime / HUNDRED;
353 localMinutes = localTime % HUNDRED;
354 localMs = static_cast<int64_t>(MakeTime(localHours, localMinutes, 0, 0));
355 if (indexPlus != CString::npos) {
356 localMs = -localMs;
357 }
358 }
359 double day = MakeDay(year, month, date);
360 double time = MakeTime(hours, minutes, seconds, ms);
361 double timeValue = TimeClip(MakeDate(day, time));
362 if (std::isnan(timeValue)) {
363 return JSTaggedValue(timeValue);
364 }
365 if (isLocal && timeValue < CHINA_1901_MS && (-localMs / MS_PER_MINUTE) == CHINA_AFTER_1901_MIN) {
366 timeValue += static_cast<double>(localMs - CHINA_BEFORE_1901_MS);
367 } else {
368 timeValue += localMs;
369 }
370 return JSTaggedValue(timeValue);
371 }
372 // static
IsoParseStringToMs(const CString & str)373 JSTaggedValue JSDate::IsoParseStringToMs(const CString &str)
374 {
375 char flag = 0;
376 int year;
377 int month = 1;
378 int date = 1;
379 int hours = 0;
380 int minutes = 0;
381 int seconds = 0;
382 int ms = 0;
383 int index = 0;
384 int len = str.length();
385 year = GetSignedNumFromString(str, len, &index);
386 CString::size_type indexT = str.find(FLAG_TIME, index);
387 CString::size_type indexZ = str.find(FLAG_UTC, index);
388 CString::size_type indexEndFlag = 0;
389 int64_t localMs = 0;
390 if (indexZ != CString::npos) {
391 indexEndFlag = indexZ;
392 } else if (len >= MIN_LENGTH && str.at(len - INDEX_PLUS_NEG) == NEG) {
393 indexEndFlag = len - INDEX_PLUS_NEG;
394 flag = NEG;
395 } else if (len >= MIN_LENGTH && str.at(len - INDEX_PLUS_NEG) == PLUS) {
396 indexEndFlag = len - INDEX_PLUS_NEG;
397 flag = PLUS;
398 }
399 if (indexT != CString::npos) {
400 if ((indexT - index) == LENGTH_PER_TIME) {
401 GetNumFromString(str, len, &index, &month);
402 } else if ((indexT - index) == (LENGTH_PER_TIME + LENGTH_PER_TIME)) {
403 GetNumFromString(str, len, &index, &month);
404 GetNumFromString(str, len, &index, &date);
405 }
406 GetNumFromString(str, len, &index, &hours);
407 GetNumFromString(str, len, &index, &minutes);
408 if (indexEndFlag > 0) {
409 if (indexEndFlag - index == LENGTH_PER_TIME) {
410 GetNumFromString(str, len, &index, &seconds);
411 } else if (indexEndFlag - index == (LENGTH_PER_TIME + LENGTH_PER_TIME + 1)) {
412 GetNumFromString(str, len, &index, &seconds);
413 GetNumFromString(str, len, &index, &ms);
414 }
415 } else {
416 if (len - index == LENGTH_PER_TIME) {
417 GetNumFromString(str, len, &index, &seconds);
418 } else if (len - index == (LENGTH_PER_TIME + LENGTH_PER_TIME + 1)) {
419 GetNumFromString(str, len, &index, &seconds);
420 GetNumFromString(str, len, &index, &ms);
421 }
422 }
423 } else {
424 GetNumFromString(str, len, &index, &month);
425 GetNumFromString(str, len, &index, &date);
426 }
427 if (indexEndFlag > 0) {
428 int localHours = 0;
429 int localMinutes = 0;
430 if (indexZ == CString::npos) {
431 GetNumFromString(str, len, &index, &localHours);
432 GetNumFromString(str, len, &index, &localMinutes);
433 if (flag == PLUS) {
434 localMs = static_cast<int64_t>(-MakeTime(localHours, localMinutes, 0, 0));
435 } else {
436 localMs = static_cast<int64_t>(MakeTime(localHours, localMinutes, 0, 0));
437 }
438 }
439 }
440 if (indexEndFlag == 0 && indexT != CString::npos) {
441 localMs -= (GetLocalOffsetFromOS(localMs, true) * MS_PER_MINUTE);
442 }
443
444 double day = MakeDay(year, month - 1, date);
445 double time = MakeTime(hours, minutes, seconds, ms);
446 double timeValue = TimeClip(MakeDate(day, time));
447 if (std::isnan(timeValue)) {
448 return JSTaggedValue(timeValue);
449 }
450 if (flag == 0 && timeValue < CHINA_1901_MS && (-localMs / MS_PER_MINUTE) == CHINA_AFTER_1901_MIN) {
451 timeValue += static_cast<double>(localMs - CHINA_BEFORE_1901_MS);
452 } else {
453 timeValue += localMs;
454 }
455 return JSTaggedValue(timeValue);
456 }
457
458 // 20.4.3.2 static
Parse(EcmaRuntimeCallInfo * argv)459 JSTaggedValue JSDate::Parse(EcmaRuntimeCallInfo *argv)
460 {
461 ASSERT(argv);
462 JSThread *thread = argv->GetThread();
463 const CString isoPriStr = "(^|(\\+|-)(\\d{2}))";
464 const CString isoDateStr =
465 "(((\\d{4})-(0?[1-9]|1[0-2])-(0?[1-9]|1[0-9]|2[0-9]|3[0-1]))"
466 "|((\\d{4})-(0?[1-9]|1[0-2]))|(\\d{4}))";
467 const CString isoTimeStr =
468 "((T([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])"
469 "\\.(\\d{3}))|(T([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]))|"
470 "(T([01][0-9]|2[0-3]):([0-5][0-9]))|(T([01][0-9]|2[0-3])))"
471 "($|Z|((\\+|-)(([01][0-9]|2[0-3]):([0-5][0-9]))))";
472 const CString isoRegStr = isoPriStr + isoDateStr + "($|Z|(" + isoTimeStr + "))";
473 const CString utcDateStr =
474 "^\\D*(0?[1-9]|1[0-9]|2[0-9]|3[0-1]) "
475 "(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) (\\d{4})";
476 const CString timeStr =
477 "(( ([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]))|( ([01][0-9]|2[0-3]):([0-5][0-9])))"
478 "($| *| GMT *| GMT((\\+|-)(\\d{4})) *)";
479 const CString utcRegStr = utcDateStr + "($| *| GMT *| GMT((\\+|-)(\\d{4})) *|(" + timeStr + "))";
480 const CString localDateStr =
481 "^[a-zA-Z]* (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) "
482 "(0?[1-9]|1[0-9]|2[0-9]|3[0-1]) (\\d{4})";
483 const CString localRegStr = localDateStr + "($| *| GMT *| GMT((\\+|-)(\\d{4})) *|(" + timeStr + "))";
484
485 std::regex isoReg(isoRegStr);
486 std::regex utcReg(utcRegStr);
487 std::regex localReg(localRegStr);
488 JSHandle<JSTaggedValue> msg = base::BuiltinsBase::GetCallArg(argv, 0);
489 JSHandle<JSTaggedValue> str = JSHandle<JSTaggedValue>::Cast(JSTaggedValue::ToString(thread, msg));
490 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, JSTaggedValue::Exception());
491 CString date = ConvertToString(EcmaString::Cast(str->GetTaggedObject()));
492 if (std::regex_match(date, isoReg)) {
493 return IsoParseStringToMs(date);
494 }
495 if (std::regex_match(date, utcReg)) {
496 return UtcParseStringToMs(date);
497 }
498 if (std::regex_match(date, localReg)) {
499 return LocalParseStringToMs(date);
500 }
501 return JSTaggedValue(base::NAN_VALUE);
502 }
503
504 // 20.4.3.1
Now()505 JSTaggedValue JSDate::Now()
506 {
507 // time from now is in ms.
508 int64_t ans;
509 struct timeval tv {
510 };
511 gettimeofday(&tv, nullptr);
512 ans = static_cast<int64_t>(tv.tv_sec) * MS_PER_SECOND + (tv.tv_usec / MS_PER_SECOND);
513 return JSTaggedValue(static_cast<double>(ans));
514 }
515
516 // 20.4.4.2 static
UTC(EcmaRuntimeCallInfo * argv)517 JSTaggedValue JSDate::UTC(EcmaRuntimeCallInfo *argv)
518 {
519 double year;
520 double month = 0;
521 double date = 1;
522 double hours = 0;
523 double minutes = 0;
524 double seconds = 0;
525 double ms = 0;
526 JSThread *thread = argv->GetThread();
527 JSHandle<JSTaggedValue> yearArg = base::BuiltinsBase::GetCallArg(argv, 0);
528 JSTaggedNumber yearValue = JSTaggedValue::ToNumber(thread, yearArg);
529 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, JSTaggedValue::Exception());
530 if (yearValue.IsNumber()) {
531 year = yearValue.GetNumber();
532 if (std::isfinite(year) && !yearValue.IsInt()) {
533 year = NumberHelper::TruncateDouble(year);
534 }
535 if (year >= 0 && year <= (HUNDRED - 1)) {
536 year = year + NINETEEN_HUNDRED_YEAR;
537 }
538 } else {
539 year = base::NAN_VALUE;
540 }
541 uint32_t index = 1;
542 uint32_t numArgs = argv->GetArgsNumber();
543 JSTaggedValue res;
544 if (numArgs > index) {
545 JSHandle<JSTaggedValue> value = base::BuiltinsBase::GetCallArg(argv, index);
546 res = JSTaggedValue::ToNumber(thread, value);
547 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, JSTaggedValue::Exception());
548 month = res.GetNumber();
549 index++;
550 }
551 if (numArgs > index) {
552 JSHandle<JSTaggedValue> value = base::BuiltinsBase::GetCallArg(argv, index);
553 res = JSTaggedValue::ToNumber(thread, value);
554 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, JSTaggedValue::Exception());
555 date = res.GetNumber();
556 index++;
557 }
558 if (numArgs > index) {
559 JSHandle<JSTaggedValue> value = base::BuiltinsBase::GetCallArg(argv, index);
560 res = JSTaggedValue::ToNumber(thread, value);
561 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, JSTaggedValue::Exception());
562 hours = res.GetNumber();
563 index++;
564 }
565 if (numArgs > index) {
566 JSHandle<JSTaggedValue> value = base::BuiltinsBase::GetCallArg(argv, index);
567 res = JSTaggedValue::ToNumber(thread, value);
568 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, JSTaggedValue::Exception());
569 minutes = res.GetNumber();
570 index++;
571 }
572 if (numArgs > index) {
573 JSHandle<JSTaggedValue> value = base::BuiltinsBase::GetCallArg(argv, index);
574 res = JSTaggedValue::ToNumber(thread, value);
575 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, JSTaggedValue::Exception());
576 seconds = res.GetNumber();
577 index++;
578 }
579 if (numArgs > index) {
580 JSHandle<JSTaggedValue> value = base::BuiltinsBase::GetCallArg(argv, index);
581 res = JSTaggedValue::ToNumber(thread, value);
582 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, JSTaggedValue::Exception());
583 ms = res.GetNumber();
584 }
585 double day = MakeDay(year, month, date);
586 double time = MakeTime(hours, minutes, seconds, ms);
587 return JSTaggedValue(TimeClip(MakeDate(day, time)));
588 }
589
GetLocalOffsetFromOS(int64_t timeMs,bool isLocal)590 int64_t JSDate::GetLocalOffsetFromOS(int64_t timeMs, bool isLocal)
591 {
592 // Preserve the old behavior for non-ICU implementation by ignoring both timeMs and is_utc.
593 if (!isLocal) {
594 return 0;
595 }
596 timeMs /= JSDate::THOUSAND;
597 time_t tv = std::time(reinterpret_cast<time_t *>(&timeMs));
598 struct tm tm {
599 };
600 // localtime_r is only suitable for linux.
601 struct tm *t = localtime_r(&tv, &tm);
602 // tm_gmtoff includes any daylight savings offset.
603 return t->tm_gmtoff / SEC_PER_MINUTE;
604 }
605
606 // 20.4.4.10
GetTime() const607 JSTaggedValue JSDate::GetTime() const
608 {
609 return GetTimeValue();
610 }
611
612 // static
StrToTargetLength(const CString & str,int length)613 CString JSDate::StrToTargetLength(const CString &str, int length)
614 {
615 int len;
616 if (str[0] == NEG) {
617 len = static_cast<int>(str.length() - 1);
618 } else {
619 len = static_cast<int>(str.length());
620 }
621 int dif = length - len;
622 CString sub;
623 for (int i = 0; i < dif; i++) {
624 sub += '0';
625 }
626 if (str[0] == NEG) {
627 sub = NEG + sub + str.substr(1, len);
628 } else {
629 sub = sub + str;
630 }
631 return sub;
632 }
633
GetThisDateValues(std::array<int64_t,DATE_LENGTH> * date,bool isLocal) const634 bool JSDate::GetThisDateValues(std::array<int64_t, DATE_LENGTH> *date, bool isLocal) const
635 {
636 double timeMs = this->GetTimeValue().GetDouble();
637 if (std::isnan(timeMs)) {
638 return false;
639 }
640 GetDateValues(timeMs, date, isLocal);
641 return true;
642 }
643
644 // 20.4.4.35
ToDateString(JSThread * thread) const645 JSTaggedValue JSDate::ToDateString(JSThread *thread) const
646 {
647 std::array<CString, MOUTH_PER_YEAR> monthName = {
648 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
649 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
650 };
651 std::array<CString, DAY_PER_WEEK> weekdayName = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
652 std::array<int64_t, DATE_LENGTH> fields = {0};
653 if (!GetThisDateValues(&fields, true)) {
654 return JSTaggedValue(base::NAN_VALUE);
655 }
656 CString year = StrToTargetLength(ToCString(fields[YEAR]), STR_LENGTH_YEAR);
657 CString day = StrToTargetLength(ToCString(fields[DAYS]), STR_LENGTH_OTHERS);
658 CString str = weekdayName[fields[WEEKDAY]] + SPACE + monthName[fields[MONTH]] + SPACE + day + SPACE + year;
659 JSHandle<EcmaString> result = thread->GetEcmaVM()->GetFactory()->NewFromCanBeCompressString(str);
660 return result.GetTaggedValue();
661 }
662
663 // static
ToDateString(double timeMs)664 CString JSDate::ToDateString(double timeMs)
665 {
666 if (std::isnan(timeMs)) {
667 return "Invalid Date";
668 }
669 std::array<CString, MOUTH_PER_YEAR> monthName = {
670 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
671 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
672 };
673 std::array<CString, DAY_PER_WEEK> weekdayName = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
674 std::array<int64_t, DATE_LENGTH> fields = {0};
675 GetDateValues(timeMs, &fields, true);
676 CString localTime;
677 int localMin = 0;
678 localMin = GetLocalOffsetFromOS(localMin, true);
679 if (timeMs < CHINA_BEFORE_1900_MS && localMin == CHINA_AFTER_1901_MIN) {
680 localMin = CHINA_BEFORE_1901_MIN;
681 }
682 if (localMin >= 0) {
683 localTime += PLUS;
684 } else if (localMin < 0) {
685 localTime += NEG;
686 localMin = -localMin;
687 }
688 localTime = localTime + StrToTargetLength(ToCString(localMin / MINUTE_PER_HOUR), STR_LENGTH_OTHERS);
689 localTime = localTime + StrToTargetLength(ToCString(localMin % MINUTE_PER_HOUR), STR_LENGTH_OTHERS);
690 CString year = ToCString(fields[YEAR]);
691 year = StrToTargetLength(year, STR_LENGTH_YEAR);
692 CString weekday = weekdayName[fields[WEEKDAY]];
693 CString month = monthName[fields[MONTH]];
694 CString day = StrToTargetLength(ToCString(fields[DAYS]), STR_LENGTH_OTHERS);
695 CString hour = StrToTargetLength(ToCString(fields[HOUR]), STR_LENGTH_OTHERS);
696 CString minute = StrToTargetLength(ToCString(fields[MIN]), STR_LENGTH_OTHERS);
697 CString second = StrToTargetLength(ToCString(fields[SEC]), STR_LENGTH_OTHERS);
698 CString str = weekday + SPACE + month + SPACE + day + SPACE + year + SPACE + hour + COLON + minute + COLON +
699 second + SPACE + "GMT" + localTime;
700 return str;
701 }
702 // 20.4.4.36
ToISOString(JSThread * thread) const703 JSTaggedValue JSDate::ToISOString(JSThread *thread) const
704 {
705 std::array<int64_t, DATE_LENGTH> fields = {0};
706 if (!GetThisDateValues(&fields, false)) {
707 return JSTaggedValue(base::NAN_VALUE);
708 }
709 CString year = ToCString(fields[YEAR]);
710 if (year[0] == NEG) {
711 year = StrToTargetLength(year, STR_LENGTH_YEAR + STR_LENGTH_OTHERS);
712 } else if (year.length() > STR_LENGTH_YEAR) {
713 year = PLUS + StrToTargetLength(year, STR_LENGTH_YEAR + STR_LENGTH_OTHERS);
714 } else {
715 year = StrToTargetLength(year, STR_LENGTH_YEAR);
716 }
717 CString month = StrToTargetLength(ToCString(fields[MONTH] + 1), STR_LENGTH_OTHERS);
718 CString day = StrToTargetLength(ToCString(fields[DAYS]), STR_LENGTH_OTHERS);
719 CString hour = StrToTargetLength(ToCString(fields[HOUR]), STR_LENGTH_OTHERS);
720 CString minute = StrToTargetLength(ToCString(fields[MIN]), STR_LENGTH_OTHERS);
721 CString second = StrToTargetLength(ToCString(fields[SEC]), STR_LENGTH_OTHERS);
722 CString ms = StrToTargetLength(ToCString(fields[MS]), STR_LENGTH_OTHERS + 1);
723 CString str =
724 year + NEG + month + NEG + day + FLAG_TIME + hour + COLON + minute + COLON + second + POINT + ms + FLAG_UTC;
725 return thread->GetEcmaVM()->GetFactory()->NewFromCanBeCompressString(str).GetTaggedValue();
726 }
727
GetLocaleTimeStr(const std::array<int64_t,DATE_LENGTH> & fields) const728 CString JSDate::GetLocaleTimeStr(const std::array<int64_t, DATE_LENGTH> &fields) const
729 {
730 CString hour;
731 if (fields[HOUR] > MOUTH_PER_YEAR) {
732 hour = ToCString(fields[HOUR] - MOUTH_PER_YEAR);
733 } else {
734 hour = ToCString(fields[HOUR]);
735 }
736 CString minute = ToCString(fields[MIN]);
737 CString second = StrToTargetLength(ToCString(fields[SEC]), STR_LENGTH_OTHERS);
738 CString str = hour + COLON + minute + COLON + second;
739 if (fields[HOUR] >= MOUTH_PER_YEAR) {
740 str = "下午" + str;
741 } else {
742 str = "上午" + str;
743 }
744 return str;
745 }
746
GetLocaleDateStr(const std::array<int64_t,DATE_LENGTH> & fields) const747 CString JSDate::GetLocaleDateStr(const std::array<int64_t, DATE_LENGTH> &fields) const
748 {
749 CString year;
750 if (fields[YEAR] < 0) {
751 year = ToCString(-fields[YEAR] + 1);
752 } else {
753 year = ToCString(fields[YEAR]);
754 }
755 CString month = ToCString(fields[MONTH] + 1);
756 CString day = ToCString(fields[DAYS]);
757 CString str = year + VIRGULE + month + VIRGULE + day;
758 return str;
759 }
760
761 // 20.4.4.38
ToLocaleDateString(JSThread * thread) const762 JSTaggedValue JSDate::ToLocaleDateString(JSThread *thread) const
763 {
764 std::array<int64_t, DATE_LENGTH> fields = {0};
765 if (!GetThisDateValues(&fields, true)) {
766 return JSTaggedValue(base::NAN_VALUE);
767 }
768 CString str = GetLocaleDateStr(fields);
769 return thread->GetEcmaVM()->GetFactory()->NewFromCanBeCompressString(str).GetTaggedValue();
770 }
771
772 // 20.4.4.39
ToLocaleString(JSThread * thread) const773 JSTaggedValue JSDate::ToLocaleString(JSThread *thread) const
774 {
775 std::array<int64_t, DATE_LENGTH> fields = {0};
776 if (!GetThisDateValues(&fields, true)) {
777 return JSTaggedValue(base::NAN_VALUE);
778 }
779 CString strDate = GetLocaleDateStr(fields);
780 CString strTime = GetLocaleTimeStr(fields);
781 return thread->GetEcmaVM()->GetFactory()->NewFromCanBeCompressString(strDate + SPACE + strTime).GetTaggedValue();
782 }
783
784 // 20.4.4.40
ToLocaleTimeString(JSThread * thread) const785 JSTaggedValue JSDate::ToLocaleTimeString(JSThread *thread) const
786 {
787 std::array<int64_t, DATE_LENGTH> fields = {0};
788 if (!GetThisDateValues(&fields, true)) {
789 return JSTaggedValue(base::NAN_VALUE);
790 }
791 CString str = GetLocaleTimeStr(fields);
792 return thread->GetEcmaVM()->GetFactory()->NewFromCanBeCompressString(str).GetTaggedValue();
793 }
794
795 // 20.4.4.41
ToString(JSThread * thread) const796 JSTaggedValue JSDate::ToString(JSThread *thread) const
797 {
798 std::array<CString, DAY_PER_WEEK> weekdayName = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
799 std::array<CString, MOUTH_PER_YEAR> monthName = {
800 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
801 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
802 };
803 int localMin = 0;
804 std::array<int64_t, DATE_LENGTH> fields = {0};
805 if (!GetThisDateValues(&fields, true)) {
806 return JSTaggedValue(base::NAN_VALUE);
807 }
808 CString localTime;
809 localMin = GetLocalOffsetFromOS(localMin, true);
810 if (static_cast<int64_t>(this->GetTimeValue().GetDouble()) < CHINA_BEFORE_1900_MS &&
811 localMin == CHINA_AFTER_1901_MIN) {
812 localMin = CHINA_BEFORE_1901_MIN;
813 }
814 if (localMin >= 0) {
815 localTime += PLUS;
816 } else if (localMin < 0) {
817 localTime += NEG;
818 localMin = -localMin;
819 }
820 localTime = localTime + StrToTargetLength(ToCString(localMin / MINUTE_PER_HOUR), STR_LENGTH_OTHERS);
821 localTime = localTime + StrToTargetLength(ToCString(localMin % MINUTE_PER_HOUR), STR_LENGTH_OTHERS);
822 CString year = ToCString(fields[YEAR]);
823 year = StrToTargetLength(year, STR_LENGTH_YEAR);
824 CString weekday = weekdayName[fields[WEEKDAY]];
825 CString month = monthName[fields[MONTH]];
826 CString day = StrToTargetLength(ToCString(fields[DAYS]), STR_LENGTH_OTHERS);
827 CString hour = StrToTargetLength(ToCString(fields[HOUR]), STR_LENGTH_OTHERS);
828 CString minute = StrToTargetLength(ToCString(fields[MIN]), STR_LENGTH_OTHERS);
829 CString second = StrToTargetLength(ToCString(fields[SEC]), STR_LENGTH_OTHERS);
830 CString str = weekday + SPACE + month + SPACE + day + SPACE + year + SPACE + hour + COLON + minute + COLON +
831 second + SPACE + "GMT" + localTime;
832 return thread->GetEcmaVM()->GetFactory()->NewFromCanBeCompressString(str).GetTaggedValue();
833 }
834
835 // 20.4.4.42
ToTimeString(JSThread * thread) const836 JSTaggedValue JSDate::ToTimeString(JSThread *thread) const
837 {
838 int localMin = 0;
839 std::array<int64_t, DATE_LENGTH> fields = {0};
840 if (!GetThisDateValues(&fields, true)) {
841 return JSTaggedValue(base::NAN_VALUE);
842 }
843 CString localTime;
844 localMin = GetLocalOffsetFromOS(localMin, true);
845 if (static_cast<int64_t>(this->GetTimeValue().GetDouble()) < CHINA_BEFORE_1900_MS &&
846 localMin == CHINA_AFTER_1901_MIN) {
847 localMin = CHINA_BEFORE_1901_MIN;
848 }
849 if (localMin >= 0) {
850 localTime += PLUS;
851 } else {
852 localTime += NEG;
853 localMin = -localMin;
854 }
855 localTime = localTime + StrToTargetLength(ToCString(localMin / MINUTE_PER_HOUR), STR_LENGTH_OTHERS);
856 localTime = localTime + StrToTargetLength(ToCString(localMin % MINUTE_PER_HOUR), STR_LENGTH_OTHERS);
857 CString hour = StrToTargetLength(ToCString(fields[HOUR]), STR_LENGTH_OTHERS);
858 CString minute = StrToTargetLength(ToCString(fields[MIN]), STR_LENGTH_OTHERS);
859 CString second = StrToTargetLength(ToCString(fields[SEC]), STR_LENGTH_OTHERS);
860 CString str = hour + COLON + minute + COLON + second + SPACE + "GMT" + localTime;
861 return thread->GetEcmaVM()->GetFactory()->NewFromCanBeCompressString(str).GetTaggedValue();
862 }
863
864 // 20.4.4.43
ToUTCString(JSThread * thread) const865 JSTaggedValue JSDate::ToUTCString(JSThread *thread) const
866 {
867 std::array<CString, DAY_PER_WEEK> weekdayName = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
868 std::array<CString, MOUTH_PER_YEAR> monthName = {
869 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
870 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
871 };
872 std::array<int64_t, DATE_LENGTH> fields = {0};
873 if (!GetThisDateValues(&fields, false)) {
874 return JSTaggedValue(base::NAN_VALUE);
875 }
876 CString year = ToCString(fields[YEAR]);
877 year = StrToTargetLength(year, STR_LENGTH_YEAR);
878 CString weekday = weekdayName[fields[WEEKDAY]];
879 CString month = monthName[fields[MONTH]];
880 CString day = StrToTargetLength(ToCString(fields[DAYS]), STR_LENGTH_OTHERS);
881 CString hour = StrToTargetLength(ToCString(fields[HOUR]), STR_LENGTH_OTHERS);
882 CString minute = StrToTargetLength(ToCString(fields[MIN]), STR_LENGTH_OTHERS);
883 CString second = StrToTargetLength(ToCString(fields[SEC]), STR_LENGTH_OTHERS);
884 CString ms = StrToTargetLength(ToCString(fields[MS]), STR_LENGTH_OTHERS);
885 CString str = weekday + COMMA + SPACE + day + SPACE + month + SPACE + year + SPACE + hour + COLON + minute + COLON +
886 second + SPACE + "GMT";
887 return thread->GetEcmaVM()->GetFactory()->NewFromCanBeCompressString(str).GetTaggedValue();
888 }
889
890 // 20.4.4.44
ValueOf() const891 JSTaggedValue JSDate::ValueOf() const
892 {
893 return this->GetTimeValue();
894 }
895
896 // static
GetDateValues(double timeMs,std::array<int64_t,DATE_LENGTH> * date,bool isLocal)897 void JSDate::GetDateValues(double timeMs, std::array<int64_t, DATE_LENGTH> *date, bool isLocal)
898 {
899 int64_t tz = 0;
900 int64_t timeMsInt;
901 int month = 0;
902 timeMsInt = static_cast<int64_t>(timeMs);
903 if (isLocal) { // timezone offset
904 tz = GetLocalOffsetFromOS(timeMsInt, isLocal);
905 if (timeMsInt < CHINA_BEFORE_1900_MS && tz == CHINA_AFTER_1901_MIN) {
906 timeMsInt += CHINA_BEFORE_1901_ADDMS;
907 timeMsInt += tz * MS_PER_SECOND * SEC_PER_MINUTE;
908 tz = CHINA_BEFORE_1901_MIN;
909 } else {
910 timeMsInt += tz * MS_PER_SECOND * SEC_PER_MINUTE;
911 }
912 }
913
914 DateUtils::TransferTimeToDate(timeMsInt, date);
915
916 int index = DateUtils::IsLeap((*date)[YEAR]) ? 1 : 0;
917 int left = 0;
918 int right = MONTH_PER_YEAR;
919 while (left <= right) {
920 int middle = (left + right) / 2; // 2 : half
921 if (DAYS_FROM_MONTH[index][middle] <= (*date)[DAYS] && DAYS_FROM_MONTH[index][middle + 1] > (*date)[DAYS]) {
922 month = middle;
923 (*date)[DAYS] -= DAYS_FROM_MONTH[index][month];
924 break;
925 } else if ((*date)[DAYS] > DAYS_FROM_MONTH[index][middle]) { // NOLINT(readability-else-after-return)
926 left = middle + 1;
927 } else if ((*date)[DAYS] < DAYS_FROM_MONTH[index][middle]) {
928 right = middle - 1;
929 }
930 }
931
932 (*date)[MONTH] = month;
933 (*date)[DAYS] = (*date)[DAYS] + 1;
934 (*date)[TIMEZONE] = -tz;
935 }
936
GetDateValue(double timeMs,uint8_t code,bool isLocal) const937 double JSDate::GetDateValue(double timeMs, uint8_t code, bool isLocal) const
938 {
939 if (std::isnan(timeMs)) {
940 return base::NAN_VALUE;
941 }
942 std::array<int64_t, DATE_LENGTH> date = {0};
943 GetDateValues(timeMs, &date, isLocal);
944 return static_cast<double>(date[code]);
945 }
946
SetDateValue(EcmaRuntimeCallInfo * argv,uint32_t code,bool isLocal) const947 JSTaggedValue JSDate::SetDateValue(EcmaRuntimeCallInfo *argv, uint32_t code, bool isLocal) const
948 {
949 // get date values.
950 std::array<int64_t, DATE_LENGTH> date = {0};
951 double timeMs = this->GetTimeValue().GetDouble();
952
953 // get values from argv.
954 uint32_t argc = argv->GetArgsNumber();
955 if (argc == 0) {
956 return JSTaggedValue(base::NAN_VALUE);
957 }
958
959 uint32_t firstValue = code & CODE_FLAG;
960 uint32_t endValue = (code >> CODE_4_BIT) & CODE_FLAG;
961 uint32_t count = endValue - firstValue;
962
963 if (argc < count) {
964 count = argc;
965 }
966
967 if (std::isnan(timeMs) && firstValue == 0) {
968 timeMs = 0.0;
969 GetDateValues(timeMs, &date, false);
970 } else {
971 GetDateValues(timeMs, &date, isLocal);
972 }
973
974 for (uint32_t i = 0; i < count; i++) {
975 JSHandle<JSTaggedValue> value = base::BuiltinsBase::GetCallArg(argv, i);
976 JSThread *thread = argv->GetThread();
977 JSTaggedNumber res = JSTaggedValue::ToNumber(thread, value);
978 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
979 double temp = res.GetNumber();
980 if (std::isnan(temp)) {
981 return JSTaggedValue(base::NAN_VALUE);
982 }
983 date[firstValue + i] = NumberHelper::TruncateDouble(temp);
984 }
985 // set date values.
986 return JSTaggedValue(SetDateValues(&date, isLocal));
987 }
988
989 // static
SetDateValues(const std::array<int64_t,DATE_LENGTH> * date,bool isLocal)990 double JSDate::SetDateValues(const std::array<int64_t, DATE_LENGTH> *date, bool isLocal)
991 {
992 int64_t month = DateUtils::Mod((*date)[MONTH], MONTH_PER_YEAR);
993 int64_t year = (*date)[YEAR] + ((*date)[MONTH] - month) / MONTH_PER_YEAR;
994 int64_t days = DateUtils::GetDaysFromYear(year);
995 int index = DateUtils::IsLeap(year) ? 1 : 0;
996 days += DAYS_FROM_MONTH[index][month];
997
998 days += (*date)[DAYS] - 1;
999 int64_t millisecond =
1000 (((*date)[HOUR] * MIN_PER_HOUR + (*date)[MIN]) * SEC_PER_MINUTE + (*date)[SEC]) * MS_PER_SECOND + (*date)[MS];
1001 int64_t result = days * MS_PER_DAY + millisecond;
1002 if (isLocal) {
1003 int64_t offset = GetLocalOffsetFromOS(result, isLocal) * SEC_PER_MINUTE * MS_PER_SECOND;
1004 if (result < CHINA_1901_MS && (offset / MS_PER_MINUTE) == CHINA_AFTER_1901_MIN) {
1005 offset += CHINA_BEFORE_1901_ADDMS;
1006 }
1007 result -= offset;
1008 }
1009 return TimeClip(result);
1010 }
1011 } // namespace panda::ecmascript
1012