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