/*
 * Copyright (c) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "ecmascript/js_date.h"

#include <ctime>
#include <regex>
#include <sys/time.h>

#include "ecmascript/base/builtins_base.h"
#include "ecmascript/date_parse.h"
#include "ecmascript/object_fast_operator-inl.h"
#include "ecmascript/platform/time.h"

namespace panda::ecmascript {
using NumberHelper = base::NumberHelper;
bool DateUtils::isCached_ = false;
int DateUtils::preSumDays_ = 0;
int DateUtils::preDays_ = 0;
int DateUtils::preMonth_ = 0;
int DateUtils::preYear_ = 0;
void DateUtils::TransferTimeToDate(int64_t timeMs, std::array<int64_t, DATE_LENGTH> *date)
{
    (*date)[HOUR] = Mod(timeMs, MS_PER_DAY);                                 // ms from hour, minutes, second, ms
    (*date)[DAYS] = (timeMs - (*date)[HOUR]) / MS_PER_DAY;                   // days from year, month, day
    (*date)[MS] = (*date)[HOUR] % MS_PER_SECOND;                             // ms
    (*date)[HOUR] = ((*date)[HOUR] - (*date)[MS]) / MS_PER_SECOND;           // s from hour, minutes, second
    (*date)[SEC] = (*date)[HOUR] % SEC_PER_MINUTE;                           // second
    (*date)[HOUR] = ((*date)[HOUR] - (*date)[SEC]) / SEC_PER_MINUTE;         // min from hour, minutes
    (*date)[MIN] = (*date)[HOUR] % SEC_PER_MINUTE;                           // min
    (*date)[HOUR] = ((*date)[HOUR] - (*date)[MIN]) / SEC_PER_MINUTE;         // hour
    (*date)[WEEKDAY] = Mod(((*date)[DAYS] + LEAP_NUMBER[0]), DAY_PER_WEEK);  // weekday
    GetYearFromDays(date);
}
// static
bool DateUtils::IsLeap(int64_t year)
{
    return year % LEAP_NUMBER[0] == 0 && (year % LEAP_NUMBER[1] != 0 || year % LEAP_NUMBER[2] == 0);  // 2: means index
}

// static
int64_t DateUtils::GetDaysInYear(int64_t year)
{
    int64_t number;
    number = IsLeap(year) ? (DAYS_IN_YEAR + 1) : DAYS_IN_YEAR;
    return number;
}

// static
int64_t DateUtils::GetDaysFromYear(int64_t year)
{
    return DAYS_IN_YEAR * (year - YEAR_NUMBER[0]) + FloorDiv(year - YEAR_NUMBER[1], LEAP_NUMBER[0]) -
           FloorDiv(year - YEAR_NUMBER[2], LEAP_NUMBER[1]) +  // 2: year index
           FloorDiv(year - YEAR_NUMBER[3], LEAP_NUMBER[2]);   // 3, 2: year index
}

// static
int64_t DateUtils::FloorDiv(int64_t a, int64_t b)
{
    ASSERT(b != 0);
    int64_t m = a % b;
    int64_t res = m < 0 ? ((a - m - b) / b) : ((a - m) / b);
    return res;
}

// static
void DateUtils::GetYearFromDays(std::array<int64_t, DATE_LENGTH> *date)
{
    if (date == nullptr) {
        return;
    }
    if (isCached_) {
        int64_t t = (*date)[DAYS];
        int64_t newDays = preDays_ + (t - preSumDays_);
        if (newDays >= 1 && newDays < DAYS_FEBRUARY) {
            preSumDays_ = t;
            preDays_ = newDays;
            (*date)[DAYS] = newDays;
            (*date)[MONTH] = preMonth_;
            (*date)[YEAR] = preYear_;
            return;
        }
    }
    int64_t realDay;
    int64_t d = (*date)[DAYS];
    preSumDays_ = d;
    d += DAYS_1970_TO_0000;                                               // shift from 1970-01-01 to 0000-03-01
    int64_t era = (d >= 0 ? d : d - DAYS_IN_400_YEARS + 1) / DAYS_IN_400_YEARS;   // an era is a 400 year period
    int64_t doe = static_cast<int64_t>(d - era * DAYS_IN_400_YEARS);              // days of era
    int64_t yoe = (doe - doe / DAYS_IN_4_YEARS + doe / DAYS_IN_100_YEARS -
                   doe / (DAYS_IN_400_YEARS - 1)) / DAYS_IN_YEAR;                 // year of era
    int64_t y = static_cast<int64_t>(yoe) + era * LEAP_NUMBER[2];
    int64_t doy = doe - (DAYS_IN_YEAR * yoe + yoe / LEAP_NUMBER[0] -
                  yoe / LEAP_NUMBER[1]);                                          // days of year
    int64_t mp = (COEFFICIENT_TO_CIVIL[0] * doy + MONTH_COEFFICIENT) /
                  COEFFICIENT_TO_CIVIL[1];                                        // [0, 11] / [Mar,Feb] system
    int64_t month = mp + (mp < MONTH_TRANSFORM[1] ?
                MONTH_TRANSFORM[0] : MONTH_TRANSFORM[2]);                         // transform month to civil system
    int64_t year = y + (month <= MONTH_COEFFICIENT);
    month -= 1;
    realDay = doy - (COEFFICIENT_TO_CIVIL[1] * mp + 2) / COEFFICIENT_TO_CIVIL[0] + 1;   // shift from 03-01 to 01-01
    (*date)[YEAR] = year;
    (*date)[MONTH] = month;
    (*date)[DAYS] = realDay;
    preDays_ = realDay;
    preMonth_ = month;
    preYear_ = year;
    isCached_ = true;
}

// static
int64_t DateUtils::Mod(int64_t a, int b)
{
    ASSERT(b != 0);
    int64_t m = a % b;
    int64_t res = m < 0 ? (m + b) : m;
    return res;
}

// static
// 20.4.1.11
double JSDate::MakeTime(double hour, double min, double sec, double ms)
{
    if (std::isfinite(hour) && std::isfinite(min) && std::isfinite(sec) && std::isfinite(ms)) {
        double hourInteger = NumberHelper::TruncateDouble(hour);
        double minInteger = NumberHelper::TruncateDouble(min);
        double secInteger = NumberHelper::TruncateDouble(sec);
        double msInteger = NumberHelper::TruncateDouble(ms);
        return hourInteger * MS_PER_HOUR + minInteger * MS_PER_MINUTE + secInteger * MS_PER_SECOND + msInteger;
    }
    return base::NAN_VALUE;
}

// static
// 20.4.1.12
double JSDate::MakeDay(double year, double month, double date)
{
    if (std::isfinite(year) && std::isfinite(month) && std::isfinite(date)) {
        double yearInteger = NumberHelper::TruncateDouble(year);
        double monthInteger = NumberHelper::TruncateDouble(month);
        int64_t y = static_cast<int64_t>(yearInteger) + static_cast<int64_t>(monthInteger / MOUTH_PER_YEAR);
        int64_t m = static_cast<int64_t>(monthInteger) % MOUTH_PER_YEAR;
        if (m < 0) {
            m += MOUTH_PER_YEAR;
            y -= 1;
        }

        int64_t days = DateUtils::GetDaysFromYear(y);
        int index = DateUtils::IsLeap(year) ? 1 : 0;
        days += DAYS_FROM_MONTH[index][m];
        return static_cast<double>(days - 1) + NumberHelper::TruncateDouble(date);
    }
    return base::NAN_VALUE;
}

// static
// 20.4.1.13
double JSDate::MakeDate(double day, double time)
{
    if (std::isfinite(day) && std::isfinite(time)) {
        return time + day * MS_PER_DAY;
    }
    return base::NAN_VALUE;
}

// static
// 20.4.1.14
double JSDate::TimeClip(double time)
{
    if (-MAX_TIME_IN_MS <= time && time <= MAX_TIME_IN_MS) {
        return NumberHelper::TruncateDouble(time);
    }
    return base::NAN_VALUE;
}

// 20.4.1.8
double JSDate::LocalTime(double timeMs) const
{
    return timeMs + GetLocalOffsetFromOS(timeMs, true);
}

// 20.4.1.9
double JSDate::UTCTime(double timeMs) const
{
    return timeMs - GetLocalOffsetFromOS(timeMs, false);
}

// static
int JSDate::GetSignedNumFromString(const CString &str, int len, int *index)
{
    int res = 0;
    GetNumFromString(str, len, index, &res);
    if (str.at(0) == NEG) {
        return -res;
    }
    return res;
}

// static
bool JSDate::GetNumFromString(const CString &str, int len, int *index, int *num)
{
    int indexStr = *index;
    char oneByte = 0;
    while (indexStr < len) {
        oneByte = str.at(indexStr);
        if (oneByte >= '0' && oneByte <= '9') {
            break;
        }
        indexStr++;
    }
    if (indexStr >= len) {
        return false;
    }
    int value = 0;
    while (indexStr < len) {
        oneByte = str.at(indexStr);
        int val = static_cast<int>(oneByte - '0');
        if (val >= 0 && val <= NUM_NINE) {
            value = value * TEN + val;
            indexStr++;
        } else {
            break;
        }
    }
    *num = value;
    *index = indexStr;
    return true;
}

// 20.4.1.7
int64_t JSDate::GetLocalOffsetInMin(const JSThread *thread, int64_t timeMs, bool isLocal)
{
    if (!isLocal) {
        return 0;
    }
    double localOffset = this->GetLocalOffset().GetDouble();
    if (localOffset == MAX_DOUBLE) {
        localOffset = static_cast<double>(GetLocalOffsetFromOS(timeMs, isLocal));
        SetLocalOffset(thread, JSTaggedValue(localOffset));
    }
    return localOffset;
}

// static
JSTaggedValue JSDate::LocalParseStringToMs(const CString &str)
{
    int year = 0;
    int month = 0;
    int date = 1;
    int hours = 0;
    int minutes = 0;
    int seconds = 0;
    int ms = 0;
    int index = 0;
    int len = static_cast<int>(str.length());
    bool isLocal = false;
    CString::size_type indexGmt;
    CString::size_type indexPlus = CString::npos;
    std::array<CString, MOUTH_PER_YEAR> monthName = {
        "Jan", "Feb", "Mar", "Apr", "May", "Jun",
        "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
        };
    int localTime = 0;
    int localHours = 0;
    int localMinutes = 0;
    int64_t localMs = 0;
    CString::size_type localSpace;
    localSpace = str.find(' ', index);
    CString strMonth = str.substr(localSpace + 1, LENGTH_MONTH_NAME);
    for (int i = 0; i < MOUTH_PER_YEAR; i++) {
        if (strMonth == monthName[i]) {
            month = i;
            break;
        }
    }
    index += (LENGTH_MONTH_NAME + 1);
    GetNumFromString(str, len, &index, &date);
    GetNumFromString(str, len, &index, &year);
    indexGmt = str.find("GMT", index);
    if (indexGmt == CString::npos) {
        GetNumFromString(str, len, &index, &hours);
        GetNumFromString(str, len, &index, &minutes);
        GetNumFromString(str, len, &index, &seconds);
        isLocal = true;
        localMs -= (GetLocalOffsetFromOS(localMs, true) * MS_PER_MINUTE);
    } else {
        indexPlus = str.find(PLUS, indexGmt);
        int indexLocal = static_cast<int>(indexGmt);
        GetNumFromString(str, indexGmt, &index, &hours);
        GetNumFromString(str, indexGmt, &index, &minutes);
        GetNumFromString(str, indexGmt, &index, &seconds);
        GetNumFromString(str, len, &indexLocal, &localTime);
        localHours = localTime / HUNDRED;
        localMinutes = localTime % HUNDRED;
        localMs = static_cast<int64_t>(MakeTime(localHours, localMinutes, 0, 0));
        if (indexPlus != CString::npos) {
            localMs = -localMs;
        }
    }
    double day = MakeDay(year, month, date);
    double time = MakeTime(hours, minutes, seconds, ms);
    double timeValue = TimeClip(MakeDate(day, time));
    if (std::isnan(timeValue)) {
        return JSTaggedValue(timeValue);
    }
    if (isLocal && timeValue < CHINA_1901_MS && (-localMs / MS_PER_MINUTE) == CHINA_AFTER_1901_MIN) {
        timeValue += static_cast<double>(localMs - CHINA_BEFORE_1901_MS);
    } else {
        timeValue += localMs;
    }
    return JSTaggedValue(timeValue);
}

// static
JSTaggedValue JSDate::UtcParseStringToMs(const CString &str)
{
    int year = 0;
    int month = 0;
    int date = 1;
    int hours = 0;
    int minutes = 0;
    int seconds = 0;
    int ms = 0;
    int index = 0;
    int len = static_cast<int>(str.length());
    CString::size_type indexGmt;
    CString::size_type indexPlus = CString::npos;
    int localTime = 0;
    int localHours = 0;
    int localMinutes = 0;
    int64_t localMs = 0;
    bool isLocal = false;
    std::array<CString, MOUTH_PER_YEAR> monthName = {
        "Jan", "Feb", "Mar", "Apr", "May", "Jun",
        "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
    };
    GetNumFromString(str, len, &index, &date);
    CString strMonth = str.substr(index + 1, LENGTH_MONTH_NAME);
    for (int i = 0; i < MOUTH_PER_YEAR; i++) {
        if (strMonth == monthName[i]) {
            month = i;
            break;
        }
    }
    index += (LENGTH_MONTH_NAME + 1);
    GetNumFromString(str, len, &index, &year);
    indexGmt = str.find("GMT", index);
    if (indexGmt == CString::npos) {
        GetNumFromString(str, len, &index, &hours);
        GetNumFromString(str, len, &index, &minutes);
        GetNumFromString(str, len, &index, &seconds);
        isLocal = true;
        localMs -= (GetLocalOffsetFromOS(localMs, true) * MS_PER_MINUTE);
    } else {
        indexPlus = str.find(PLUS, indexGmt);
        int indexLocal = static_cast<int>(indexGmt);
        GetNumFromString(str, indexGmt, &index, &hours);
        GetNumFromString(str, indexGmt, &index, &minutes);
        GetNumFromString(str, indexGmt, &index, &seconds);
        GetNumFromString(str, len, &indexLocal, &localTime);
        localHours = localTime / HUNDRED;
        localMinutes = localTime % HUNDRED;
        localMs = static_cast<int64_t>(MakeTime(localHours, localMinutes, 0, 0));
        if (indexPlus != CString::npos) {
            localMs = -localMs;
        }
    }
    double day = MakeDay(year, month, date);
    double time = MakeTime(hours, minutes, seconds, ms);
    double timeValue = TimeClip(MakeDate(day, time));
    if (std::isnan(timeValue)) {
        return JSTaggedValue(timeValue);
    }
    if (isLocal && timeValue < CHINA_1901_MS && (-localMs / MS_PER_MINUTE) == CHINA_AFTER_1901_MIN) {
        timeValue += static_cast<double>(localMs - CHINA_BEFORE_1901_MS);
    } else {
        timeValue += localMs;
    }
    return JSTaggedValue(timeValue);
}
// static
JSTaggedValue JSDate::IsoParseStringToMs(const CString &str)
{
    char flag = 0;
    int year;
    int month = 1;
    int date = 1;
    int hours = 0;
    int minutes = 0;
    int seconds = 0;
    int ms = 0;
    int index = 0;
    int len = static_cast<int>(str.length());
    year = GetSignedNumFromString(str, len, &index);
    CString::size_type indexT = str.find(FLAG_TIME, index);
    CString::size_type indexZ = str.find(FLAG_UTC, index);
    CString::size_type indexEndFlag = 0;
    int64_t localMs = 0;
    if (indexZ != CString::npos) {
        indexEndFlag = indexZ;
    } else if (len >= MIN_LENGTH && str.at(len - INDEX_PLUS_NEG) == NEG) {
        indexEndFlag = static_cast<CString::size_type>(len - INDEX_PLUS_NEG);
        flag = NEG;
    } else if (len >= MIN_LENGTH && str.at(len - INDEX_PLUS_NEG) == PLUS) {
        indexEndFlag = static_cast<CString::size_type>(len - INDEX_PLUS_NEG);
        flag = PLUS;
    }
    if (indexT != CString::npos) {
        if (static_cast<int>(indexT) - index == LENGTH_PER_TIME) {
            GetNumFromString(str, len, &index, &month);
        } else if (static_cast<int>(indexT) - index == (LENGTH_PER_TIME + LENGTH_PER_TIME)) {
            GetNumFromString(str, len, &index, &month);
            GetNumFromString(str, len, &index, &date);
        }
        GetNumFromString(str, len, &index, &hours);
        GetNumFromString(str, len, &index, &minutes);
        if (indexEndFlag > 0) {
            if (static_cast<int>(indexEndFlag) - index == LENGTH_PER_TIME) {
                GetNumFromString(str, len, &index, &seconds);
            } else if (static_cast<int>(indexEndFlag) - index == (LENGTH_PER_TIME + LENGTH_PER_TIME + 1)) {
                GetNumFromString(str, len, &index, &seconds);
                GetNumFromString(str, len, &index, &ms);
            }
        } else {
            if (len - index == LENGTH_PER_TIME) {
                GetNumFromString(str, len, &index, &seconds);
            } else if (len - index == (LENGTH_PER_TIME + LENGTH_PER_TIME + 1)) {
                GetNumFromString(str, len, &index, &seconds);
                GetNumFromString(str, len, &index, &ms);
            }
        }
    } else {
        GetNumFromString(str, len, &index, &month);
        GetNumFromString(str, len, &index, &date);
    }
    if (indexEndFlag > 0) {
        int localHours = 0;
        int localMinutes = 0;
        if (indexZ == CString::npos) {
            GetNumFromString(str, len, &index, &localHours);
            GetNumFromString(str, len, &index, &localMinutes);
            if (flag == PLUS) {
                localMs = static_cast<int64_t>(-MakeTime(localHours, localMinutes, 0, 0));
            } else {
                localMs = static_cast<int64_t>(MakeTime(localHours, localMinutes, 0, 0));
            }
        }
    }
    if (indexEndFlag == 0 && indexT != CString::npos) {
        localMs -= (GetLocalOffsetFromOS(localMs, true) * MS_PER_MINUTE);
    }

    double day = MakeDay(year, month - 1, date);
    double time = MakeTime(hours, minutes, seconds, ms);
    double timeValue = TimeClip(MakeDate(day, time));
    if (std::isnan(timeValue)) {
        return JSTaggedValue(timeValue);
    }
    if (flag == 0 && timeValue < CHINA_1901_MS && (-localMs / MS_PER_MINUTE) == CHINA_AFTER_1901_MIN) {
        timeValue += static_cast<double>(localMs - CHINA_BEFORE_1901_MS);
    } else {
        timeValue += localMs;
    }
    return JSTaggedValue(timeValue);
}

JSTaggedValue JSDate::GetTimeFromString(const char *str, int len)
{
    int time[TIMEZONE + 1];
    bool res = DateParse::ParseDateString(str, len, time);
    if (res) {
        double day = MakeDay(time[YEAR], time[MONTH], time[DAYS]);
        double dateTime = MakeTime(time[HOUR], time[MIN], time[SEC], time[MS]);
        double timeValue = TimeClip(MakeDate(day, dateTime));
        if (std::isnan(timeValue)) {
            return JSTaggedValue(timeValue);
        }
        int64_t localMs;
        if (time[TIMEZONE] == INT_MAX) {
            localMs = GetLocalOffsetFromOS(static_cast<int64_t>(timeValue), true) * MS_PER_MINUTE;
        } else {
            localMs = time[TIMEZONE] * MS_PER_SECOND;
        }
        timeValue -= localMs;
        return JSTaggedValue(timeValue);
    }
    return JSTaggedValue(base::NAN_VALUE);
}

// 20.4.3.2 static
JSTaggedValue JSDate::Parse(EcmaRuntimeCallInfo *argv)
{
    ASSERT(argv);
    JSThread *thread = argv->GetThread();
    JSHandle<JSTaggedValue> msg = base::BuiltinsBase::GetCallArg(argv, 0);
    JSHandle<EcmaString> ecmaStr = JSTaggedValue::ToString(thread, msg);
    RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, JSTaggedValue::Exception());
    CVector<uint8_t> tmpBuf;
    EcmaStringAccessor strAccessor(const_cast<EcmaString *>(*ecmaStr));
    if (strAccessor.IsUtf16()) {
        return JSTaggedValue(base::NAN_VALUE);
    }
    int len = static_cast<int>(strAccessor.GetLength());
    auto data = reinterpret_cast<const char *>(strAccessor.GetUtf8DataFlat(*ecmaStr, tmpBuf));
    return GetTimeFromString(data, len);
}

// 20.4.3.1
JSTaggedValue JSDate::Now()
{
    // time from now is in ms.
    int64_t ans;
    struct timeval tv {
    };
    gettimeofday(&tv, nullptr);
    ans = static_cast<int64_t>(tv.tv_sec) * MS_PER_SECOND + (tv.tv_usec / MS_PER_SECOND);
    return JSTaggedValue(static_cast<double>(ans));
}

// 20.4.4.2 static
JSTaggedValue JSDate::UTC(EcmaRuntimeCallInfo *argv)
{
    double year = 0.0;
    double month = 0.0;
    double date = 1.0;
    double hours = 0.0;
    double minutes = 0.0;
    double seconds = 0.0;
    double ms = 0.0;
    JSThread *thread = argv->GetThread();
    JSHandle<JSTaggedValue> yearArg = base::BuiltinsBase::GetCallArg(argv, 0);
    JSTaggedNumber yearValue = JSTaggedValue::ToNumber(thread, yearArg);
    RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, JSTaggedValue::Exception());
    if (yearValue.IsNumber()) {
        year = yearValue.GetNumber();
        if (std::isfinite(year) && !yearValue.IsInt()) {
            year = NumberHelper::TruncateDouble(year);
        }
        if (year >= 0 && year <= (HUNDRED - 1)) {
            year = year + NINETEEN_HUNDRED_YEAR;
        }
    } else {
        year = base::NAN_VALUE;
    }
    uint32_t index = 1;
    uint32_t numArgs = argv->GetArgsNumber();
    JSTaggedValue res;
    if (numArgs > index) {
        JSHandle<JSTaggedValue> value = base::BuiltinsBase::GetCallArg(argv, index);
        res = JSTaggedValue::ToNumber(thread, value);
        RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, JSTaggedValue::Exception());
        month = res.GetNumber();
        index++;
    }
    if (numArgs > index) {
        JSHandle<JSTaggedValue> value = base::BuiltinsBase::GetCallArg(argv, index);
        res = JSTaggedValue::ToNumber(thread, value);
        RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, JSTaggedValue::Exception());
        date = res.GetNumber();
        index++;
    }
    if (numArgs > index) {
        JSHandle<JSTaggedValue> value = base::BuiltinsBase::GetCallArg(argv, index);
        res = JSTaggedValue::ToNumber(thread, value);
        RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, JSTaggedValue::Exception());
        hours = res.GetNumber();
        index++;
    }
    if (numArgs > index) {
        JSHandle<JSTaggedValue> value = base::BuiltinsBase::GetCallArg(argv, index);
        res = JSTaggedValue::ToNumber(thread, value);
        RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, JSTaggedValue::Exception());
        minutes = res.GetNumber();
        index++;
    }
    if (numArgs > index) {
        JSHandle<JSTaggedValue> value = base::BuiltinsBase::GetCallArg(argv, index);
        res = JSTaggedValue::ToNumber(thread, value);
        RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, JSTaggedValue::Exception());
        seconds = res.GetNumber();
        index++;
    }
    if (numArgs > index) {
        JSHandle<JSTaggedValue> value = base::BuiltinsBase::GetCallArg(argv, index);
        res = JSTaggedValue::ToNumber(thread, value);
        RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, JSTaggedValue::Exception());
        ms = res.GetNumber();
    }
    double day = MakeDay(year, month, date);
    double time = MakeTime(hours, minutes, seconds, ms);
    return JSTaggedValue(TimeClip(MakeDate(day, time)));
}

// 20.4.4.10
JSTaggedValue JSDate::GetTime() const
{
    return GetTimeValue();
}

// static
CString JSDate::StrToTargetLength(const CString &str, int length)
{
    int len = 0;
    if (str[0] == NEG) {
        len = static_cast<int>(str.length() - 1);
    } else {
        len = static_cast<int>(str.length());
    }
    int dif = length - len;
    CString sub;
    for (int i = 0; i < dif; i++) {
        sub += '0';
    }
    if (str[0] == NEG) {
        sub = NEG + sub + str.substr(1, len);
    } else {
        sub = sub + str;
    }
    return sub;
}

bool JSDate::GetThisDateValues(std::array<int64_t, DATE_LENGTH> *date, bool isLocal) const
{
    double timeMs = this->GetTimeValue().GetDouble();
    if (std::isnan(timeMs)) {
        return false;
    }
    GetDateValues(timeMs, date, isLocal);
    return true;
}

// 20.4.4.35
JSTaggedValue JSDate::ToDateString(JSThread *thread) const
{
    std::array<CString, MOUTH_PER_YEAR> monthName = {
        "Jan", "Feb", "Mar", "Apr", "May", "Jun",
        "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
    };
    std::array<CString, DAY_PER_WEEK> weekdayName = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
    std::array<int64_t, DATE_LENGTH> fields = {0};
    if (!GetThisDateValues(&fields, true)) {
        return JSTaggedValue(base::NAN_VALUE);
    }
    CString year = StrToTargetLength(ToCString(fields[YEAR]), STR_LENGTH_YEAR);
    CString day = StrToTargetLength(ToCString(fields[DAYS]), STR_LENGTH_OTHERS);
    CString str = weekdayName[fields[WEEKDAY]] + SPACE + monthName[fields[MONTH]] + SPACE + day + SPACE + year;
    JSHandle<EcmaString> result = thread->GetEcmaVM()->GetFactory()->NewFromASCII(str);
    return result.GetTaggedValue();
}

// static
CString JSDate::ToDateString(double timeMs)
{
    if (std::isnan(timeMs)) {
        return "Invalid Date";
    }
    std::array<CString, MOUTH_PER_YEAR> monthName = {
        "Jan", "Feb", "Mar", "Apr", "May", "Jun",
        "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
    };
    std::array<CString, DAY_PER_WEEK> weekdayName = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
    std::array<int64_t, DATE_LENGTH> fields = {0};
    GetDateValues(timeMs, &fields, true);
    CString localTime;
    int localMin = 0;
    localMin = GetLocalOffsetFromOS(timeMs, true);
    if (localMin >= 0) {
        localTime += PLUS;
    } else {
        localTime += NEG;
        localMin = -localMin;
    }
    localTime = localTime + StrToTargetLength(ToCString(localMin / MINUTE_PER_HOUR), STR_LENGTH_OTHERS);
    localTime = localTime + StrToTargetLength(ToCString(localMin % MINUTE_PER_HOUR), STR_LENGTH_OTHERS);
    CString year = ToCString(fields[YEAR]);
    year = StrToTargetLength(year, STR_LENGTH_YEAR);
    CString weekday = weekdayName[fields[WEEKDAY]];
    CString month = monthName[fields[MONTH]];
    CString day = StrToTargetLength(ToCString(fields[DAYS]), STR_LENGTH_OTHERS);
    CString hour = StrToTargetLength(ToCString(fields[HOUR]), STR_LENGTH_OTHERS);
    CString minute = StrToTargetLength(ToCString(fields[MIN]), STR_LENGTH_OTHERS);
    CString second = StrToTargetLength(ToCString(fields[SEC]), STR_LENGTH_OTHERS);
    CString str = weekday + SPACE + month + SPACE + day + SPACE + year + SPACE + hour + COLON + minute + COLON +
                  second + SPACE + "GMT" + localTime;
    return str;
}
// 20.4.4.36
JSTaggedValue JSDate::ToISOString(JSThread *thread) const
{
    std::array<int64_t, DATE_LENGTH> fields = {0};
    if (!GetThisDateValues(&fields, false)) {
        return JSTaggedValue(base::NAN_VALUE);
    }
    CString year = ToCString(fields[YEAR]);
    if (year[0] == NEG) {
        year = StrToTargetLength(year, STR_LENGTH_YEAR + STR_LENGTH_OTHERS);
    } else if (year.length() > STR_LENGTH_YEAR) {
        year = PLUS + StrToTargetLength(year, STR_LENGTH_YEAR + STR_LENGTH_OTHERS);
    } else {
        year = StrToTargetLength(year, STR_LENGTH_YEAR);
    }
    CString month = StrToTargetLength(ToCString(fields[MONTH] + 1), STR_LENGTH_OTHERS);
    CString day = StrToTargetLength(ToCString(fields[DAYS]), STR_LENGTH_OTHERS);
    CString hour = StrToTargetLength(ToCString(fields[HOUR]), STR_LENGTH_OTHERS);
    CString minute = StrToTargetLength(ToCString(fields[MIN]), STR_LENGTH_OTHERS);
    CString second = StrToTargetLength(ToCString(fields[SEC]), STR_LENGTH_OTHERS);
    CString ms = StrToTargetLength(ToCString(fields[MS]), STR_LENGTH_OTHERS + 1);
    CString str =
        year + NEG + month + NEG + day + FLAG_TIME + hour + COLON + minute + COLON + second + POINT + ms + FLAG_UTC;
    return thread->GetEcmaVM()->GetFactory()->NewFromASCII(str).GetTaggedValue();
}

// 20.4.4.41
JSTaggedValue JSDate::ToString(JSThread *thread) const
{
    std::array<CString, DAY_PER_WEEK> weekdayName = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
    std::array<CString, MOUTH_PER_YEAR> monthName = {
        "Jan", "Feb", "Mar", "Apr", "May", "Jun",
        "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
    };
    int localMin = 0;
    std::array<int64_t, DATE_LENGTH> fields = {0};
    if (!GetThisDateValues(&fields, true)) {
        return JSTaggedValue(base::NAN_VALUE);
    }
    CString localTime;
    localMin = GetLocalOffsetFromOS(static_cast<int64_t>(this->GetTimeValue().GetDouble()), true);
    if (localMin >= 0) {
        localTime += PLUS;
    } else {
        localTime += NEG;
        localMin = -localMin;
    }
    localTime = localTime + StrToTargetLength(ToCString(localMin / MINUTE_PER_HOUR), STR_LENGTH_OTHERS);
    localTime = localTime + StrToTargetLength(ToCString(localMin % MINUTE_PER_HOUR), STR_LENGTH_OTHERS);
    CString year = ToCString(fields[YEAR]);
    year = StrToTargetLength(year, STR_LENGTH_YEAR);
    CString weekday = weekdayName[fields[WEEKDAY]];
    CString month = monthName[fields[MONTH]];
    CString day = StrToTargetLength(ToCString(fields[DAYS]), STR_LENGTH_OTHERS);
    CString hour = StrToTargetLength(ToCString(fields[HOUR]), STR_LENGTH_OTHERS);
    CString minute = StrToTargetLength(ToCString(fields[MIN]), STR_LENGTH_OTHERS);
    CString second = StrToTargetLength(ToCString(fields[SEC]), STR_LENGTH_OTHERS);
    CString str = weekday + SPACE + month + SPACE + day + SPACE + year + SPACE + hour + COLON + minute + COLON +
                  second + SPACE + "GMT" + localTime;
    return thread->GetEcmaVM()->GetFactory()->NewFromASCII(str).GetTaggedValue();
}

// 20.4.4.42
JSTaggedValue JSDate::ToTimeString(JSThread *thread) const
{
    int localMin = 0;
    std::array<int64_t, DATE_LENGTH> fields = {0};
    if (!GetThisDateValues(&fields, true)) {
        return JSTaggedValue(base::NAN_VALUE);
    }
    CString localTime;
    localMin = GetLocalOffsetFromOS(static_cast<int64_t>(this->GetTimeValue().GetDouble()), true);
    if (localMin >= 0) {
        localTime += PLUS;
    } else {
        localTime += NEG;
        localMin = -localMin;
    }
    localTime = localTime + StrToTargetLength(ToCString(localMin / MINUTE_PER_HOUR), STR_LENGTH_OTHERS);
    localTime = localTime + StrToTargetLength(ToCString(localMin % MINUTE_PER_HOUR), STR_LENGTH_OTHERS);
    CString hour = StrToTargetLength(ToCString(fields[HOUR]), STR_LENGTH_OTHERS);
    CString minute = StrToTargetLength(ToCString(fields[MIN]), STR_LENGTH_OTHERS);
    CString second = StrToTargetLength(ToCString(fields[SEC]), STR_LENGTH_OTHERS);
    CString str = hour + COLON + minute + COLON + second + SPACE + "GMT" + localTime;
    return thread->GetEcmaVM()->GetFactory()->NewFromASCII(str).GetTaggedValue();
}

// 20.4.4.43
JSTaggedValue JSDate::ToUTCString(JSThread *thread) const
{
    std::array<CString, DAY_PER_WEEK> weekdayName = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
    std::array<CString, MOUTH_PER_YEAR> monthName = {
        "Jan", "Feb", "Mar", "Apr", "May", "Jun",
        "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
    };
    std::array<int64_t, DATE_LENGTH> fields = {0};
    if (!GetThisDateValues(&fields, false)) {
        return JSTaggedValue(base::NAN_VALUE);
    }
    CString year = ToCString(fields[YEAR]);
    year = StrToTargetLength(year, STR_LENGTH_YEAR);
    CString weekday = weekdayName[fields[WEEKDAY]];
    CString month = monthName[fields[MONTH]];
    CString day = StrToTargetLength(ToCString(fields[DAYS]), STR_LENGTH_OTHERS);
    CString hour = StrToTargetLength(ToCString(fields[HOUR]), STR_LENGTH_OTHERS);
    CString minute = StrToTargetLength(ToCString(fields[MIN]), STR_LENGTH_OTHERS);
    CString second = StrToTargetLength(ToCString(fields[SEC]), STR_LENGTH_OTHERS);
    CString ms = StrToTargetLength(ToCString(fields[MS]), STR_LENGTH_OTHERS);
    CString str = weekday + COMMA + SPACE + day + SPACE + month + SPACE + year + SPACE + hour + COLON + minute + COLON +
                  second + SPACE + "GMT";
    return thread->GetEcmaVM()->GetFactory()->NewFromASCII(str).GetTaggedValue();
}

// 20.4.4.44
JSTaggedValue JSDate::ValueOf() const
{
    return this->GetTimeValue();
}

// static
void JSDate::GetDateValues(double timeMs, std::array<int64_t, DATE_LENGTH> *date, bool isLocal)
{
    int64_t tz = 0;
    int64_t timeMsInt;
    timeMsInt = static_cast<int64_t>(timeMs);
    bool preDst = IsDst(timeMsInt);
    if (isLocal) {  // timezone offset
        tz = GetLocalOffsetFromOS(timeMsInt, isLocal);
        timeMsInt += tz * MS_PER_SECOND * SEC_PER_MINUTE;
        bool curDst = IsDst(timeMsInt);
        int64_t dstOffset = MIN_PER_HOUR * SEC_PER_MINUTE * MS_PER_SECOND;
        if (curDst) {
            timeMsInt += preDst ? 0 : dstOffset;
        } else {
            timeMsInt -= preDst ? dstOffset : 0;
        }
    }

    DateUtils::TransferTimeToDate(timeMsInt, date);
    (*date)[TIMEZONE] = -tz;
}

double JSDate::GetDateValue(double timeMs, uint8_t code, bool isLocal) const
{
    if (std::isnan(timeMs)) {
        return base::NAN_VALUE;
    }
    std::array<int64_t, DATE_LENGTH> date = {0};
    GetDateValues(timeMs, &date, isLocal);
    return static_cast<double>(date[code]);
}

JSTaggedValue JSDate::SetDateValue(EcmaRuntimeCallInfo *argv, uint32_t code, bool isLocal) const
{
    // get date values.
    std::array<int64_t, DATE_LENGTH> date = {0};
    double timeMs = this->GetTimeValue().GetDouble();

    // get values from argv.
    uint32_t argc = argv->GetArgsNumber();
    if (argc == 0) {
        return JSTaggedValue(base::NAN_VALUE);
    }

    uint32_t firstValue = code & CODE_FLAG;
    uint32_t endValue = (code >> CODE_4_BIT) & CODE_FLAG;
    uint32_t count = endValue - firstValue;

    if (argc < count) {
        count = argc;
    }
    if (std::isnan(timeMs) && firstValue == 0) {
        timeMs = 0.0;
        GetDateValues(timeMs, &date, false);
    } else {
        GetDateValues(timeMs, &date, isLocal);
    }

    for (uint32_t i = 0; i < count; i++) {
        JSHandle<JSTaggedValue> value = base::BuiltinsBase::GetCallArg(argv, i);
        JSThread *thread = argv->GetThread();
        JSTaggedNumber res = JSTaggedValue::ToNumber(thread, value);
        RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
        double temp = res.GetNumber();
        if (std::isnan(temp)) {
            return JSTaggedValue(base::NAN_VALUE);
        }
        date[firstValue + i] = NumberHelper::TruncateDouble(temp);
    }
    // set date values.
    return JSTaggedValue(SetDateValues(&date, isLocal));
}

// static
double JSDate::SetDateValues(const std::array<int64_t, DATE_LENGTH> *date, bool isLocal)
{
    int64_t month = DateUtils::Mod((*date)[MONTH], MONTH_PER_YEAR);
    int64_t year = (*date)[YEAR] + ((*date)[MONTH] - month) / MONTH_PER_YEAR;
    int64_t days = DateUtils::GetDaysFromYear(year);
    int index = DateUtils::IsLeap(year) ? 1 : 0;
    days += DAYS_FROM_MONTH[index][month];

    days += (*date)[DAYS] - 1;
    int64_t millisecond =
        (((*date)[HOUR] * MIN_PER_HOUR + (*date)[MIN]) * SEC_PER_MINUTE + (*date)[SEC]) * MS_PER_SECOND + (*date)[MS];
    int64_t result = days * MS_PER_DAY + millisecond;
    if (isLocal) {
        int64_t offset = GetLocalOffsetFromOS(result, isLocal) * SEC_PER_MINUTE * MS_PER_SECOND;
        result -= offset;
    }
    return TimeClip(result);
}

double JSDate::SetDateValues(int64_t year, int64_t month, int64_t day)
{
    if (year >= 0 && year < HUNDRED) {
        year += NINETEEN_HUNDRED_YEAR;
    }
    int64_t m = DateUtils::Mod(month, MONTH_PER_YEAR);
    int64_t y = year + (month - m) / MONTH_PER_YEAR;
    int64_t d = DateUtils::GetDaysFromYear(y);
    int index = DateUtils::IsLeap(y) ? 1 : 0;
    d += DAYS_FROM_MONTH[index][m] + day - 1;
    int64_t result = d * MS_PER_DAY;

    int64_t offset = GetLocalOffsetFromOS(result, true) * SEC_PER_MINUTE * MS_PER_SECOND;
    result -= offset;
    return TimeClip(result);
}
}  // namespace panda::ecmascript