/* Copyright JS Foundation and other contributors, http://js.foundation * * 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 #include "ecma-alloc.h" #include "ecma-builtin-helpers.h" #include "ecma-exceptions.h" #include "ecma-globals.h" #include "ecma-helpers.h" #include "ecma-objects.h" #include "lit-char-helpers.h" #if JERRY_BUILTIN_DATE /** \addtogroup ecma ECMA * @{ * * \addtogroup ecmabuiltinhelpers ECMA builtin helper operations * @{ */ /** * Day names */ const char day_names_p[7][3] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; /** * Month names */ const char month_names_p[12][3] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; /** * Calculate the elapsed days since Unix Epoch * * @return elapsed days since Unix Epoch */ extern inline int32_t JERRY_ATTR_ALWAYS_INLINE ecma_date_day_from_time (ecma_number_t time) /**< time value */ { JERRY_ASSERT (!ecma_number_is_nan (time)); if (time < 0) { time -= ECMA_DATE_MS_PER_DAY - 1; } return (int32_t) (time / ECMA_DATE_MS_PER_DAY); } /* ecma_date_day_from_time */ /** * Abstract operation: DayFromYear * * See also: * ECMA-262 v11, 20.4.1.3 * * @return first of day in the given year */ static int32_t ecma_date_day_from_year (int32_t year) /**< year value */ { if (JERRY_LIKELY (year >= 1970)) { return (int32_t) (365 * (year - 1970) + ((year - 1969) / 4) - ((year - 1901) / 100) + ((year - 1601) / 400)); } return (int32_t) (365 * (year - 1970) + floor ((year - 1969) / 4.0) - floor ((year - 1901) / 100.0) + floor ((year - 1601) / 400.0)); } /* ecma_date_day_from_year */ /** * Abstract operation: DaysInYear * * See also: * ECMA-262 v11, 20.4.1.3 * * @return number of days in the given year */ static int ecma_date_days_in_year (int32_t year) /**< year */ { if (year % 4 != 0 || (year % 100 == 0 && (year % 400 != 0))) { return ECMA_DATE_DAYS_IN_YEAR; } return ECMA_DATE_DAYS_IN_LEAP_YEAR; } /* ecma_date_days_in_year */ /** * Abstract operation: InLeapYear * * See also: * ECMA-262 v11, 20.4.1.3 * * @return 1 - if the year is leap * 0 - otherwise */ static int32_t ecma_date_in_leap_year (int32_t year) /**< time value */ { return ecma_date_days_in_year (year) - ECMA_DATE_DAYS_IN_YEAR; } /* ecma_date_in_leap_year */ /** * First days of months in normal and leap years */ static const uint16_t first_day_in_month[2][12] = { { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, /* normal year */ } , { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335 /* leap year */ } }; /** * Abstract operation: YearFromTime * * See also: * ECMA-262 v11, 20.4.1.3 * * @return year corresponds to the given time */ int32_t ecma_date_year_from_time (ecma_number_t time) /**< time value */ { JERRY_ASSERT (!ecma_number_is_nan (time)); int32_t approx = (int32_t) (floor (time / ECMA_DATE_MS_PER_DAY / 365.2425) + 1970); int64_t year_ms = ecma_date_day_from_year (approx) * ((int64_t) ECMA_DATE_MS_PER_DAY); if (year_ms > time) { approx--; } if (year_ms + ecma_date_days_in_year (approx) * ((int64_t) ECMA_DATE_MS_PER_DAY) <= time) { approx++; } return approx; } /* ecma_date_year_from_time */ /** * Abstract operation: MonthFromTime * * See also: * ECMA-262 v11, 20.4.1.4 * * @return month corresponds to the given time */ int32_t ecma_date_month_from_time (ecma_number_t time) /**< time value */ { JERRY_ASSERT (!ecma_number_is_nan (time)); int32_t year = ecma_date_year_from_time (time); int32_t day_within_year = ecma_date_day_from_time (time) - ecma_date_day_from_year (year); JERRY_ASSERT (day_within_year >= 0 && day_within_year < ECMA_DATE_DAYS_IN_LEAP_YEAR); int32_t in_leap_year = ecma_date_in_leap_year (year); for (int i = 1; i < 12; i++) { if (day_within_year < first_day_in_month[in_leap_year][i]) { return i - 1; } } return 11; } /* ecma_date_month_from_time */ /** * Abstract operation: DateFromTime * * See also: * ECMA-262 v11, 20.4.1.4 * * @return date corresponds to the given time */ int32_t ecma_date_date_from_time (ecma_number_t time) /**< time value */ { JERRY_ASSERT (!ecma_number_is_nan (time)); int32_t year = ecma_date_year_from_time (time); int32_t day_within_year = ecma_date_day_from_time (time) - ecma_date_day_from_year (year); JERRY_ASSERT (day_within_year >= 0 && day_within_year < ECMA_DATE_DAYS_IN_LEAP_YEAR); int32_t in_leap_year = ecma_date_in_leap_year (year); int32_t month = 11; for (int i = 1; i < 12; i++) { if (day_within_year < first_day_in_month[in_leap_year][i]) { month = i - 1; break; } } return day_within_year + 1 - first_day_in_month[in_leap_year][month]; } /* ecma_date_date_from_time */ /** * Abstract operation: WeekDay * * See also: * ECMA-262 v11, 20.4.1.4 * * @return weekday corresponds to the given time */ int32_t ecma_date_week_day (ecma_number_t time) /**< time value */ { JERRY_ASSERT (!ecma_number_is_nan (time)); int32_t day = ecma_date_day_from_time (time); int week_day = (day + 4) % 7; return week_day >= 0 ? week_day : week_day + 7; } /* ecma_date_week_day */ /** * Abstract operation: LocalTZA * * See also: * ECMA-262 v11, 20.4.1.7 * * @return local time zone adjustment */ extern inline ecma_number_t JERRY_ATTR_ALWAYS_INLINE ecma_date_local_time_zone_adjustment (ecma_number_t time) /**< time value */ { return jerry_port_get_local_time_zone_adjustment (time, true); } /* ecma_date_local_time_zone_adjustment */ /** * Abstract operation: UTC * * See also: * ECMA-262 v11, 20.4.1.9 * * @return UTC time */ ecma_number_t ecma_date_utc (ecma_number_t time) /**< time value */ { return time - jerry_port_get_local_time_zone_adjustment (time, false); } /* ecma_date_utc */ /** * Calculate the time component from the given time * * @return time component of the given time */ int32_t ecma_date_time_in_day_from_time (ecma_number_t time) /**< time value */ { JERRY_ASSERT (!ecma_number_is_nan (time)); ecma_number_t day = ecma_date_day_from_time (time); return (int32_t) (time - (day * ECMA_DATE_MS_PER_DAY)); } /* ecma_date_time_in_day_from_time */ /** * Abstract operation: HourFromTime * * See also: * ECMA-262 v11, 20.4.1.10 * * @return hours component of the given time */ int32_t ecma_date_hour_from_time (ecma_number_t time) /**< time value */ { JERRY_ASSERT (!ecma_number_is_nan (time)); int32_t time_in_day = ecma_date_time_in_day_from_time (time); return (int32_t) (time_in_day / ECMA_DATE_MS_PER_HOUR); } /* ecma_date_hour_from_time */ /** * Abstract operation: HourFromTime * * See also: * ECMA-262 v11, 20.4.1.10 * * @return minutes component of the given time */ int32_t ecma_date_min_from_time (ecma_number_t time) /**< time value */ { JERRY_ASSERT (!ecma_number_is_nan (time)); int32_t time_in_day = ecma_date_time_in_day_from_time (time); return ((int32_t) (time_in_day / ECMA_DATE_MS_PER_MINUTE)) % ECMA_DATE_MINUTES_PER_HOUR; } /* ecma_date_min_from_time */ /** * Abstract operation: HourFromTime * * See also: * ECMA-262 v11, 20.4.1.10 * * @return seconds component of the given time */ int32_t ecma_date_sec_from_time (ecma_number_t time) /**< time value */ { JERRY_ASSERT (!ecma_number_is_nan (time)); int32_t time_in_day = ecma_date_time_in_day_from_time (time); return ((int32_t) (time_in_day / ECMA_DATE_MS_PER_SECOND)) % ECMA_DATE_SECONDS_PER_MINUTE; } /* ecma_date_sec_from_time */ /** * Abstract operation: HourFromTime * * See also: * ECMA-262 v11, 20.4.1.10 * * @return milliseconds component of the given time */ int32_t ecma_date_ms_from_time (ecma_number_t time) /**< time value */ { JERRY_ASSERT (!ecma_number_is_nan (time)); int32_t time_in_day = ecma_date_time_in_day_from_time (time); return (int32_t) (time_in_day % ECMA_DATE_MS_PER_SECOND); } /* ecma_date_ms_from_time */ /** * Abstract operation: MakeTime * * See also: * ECMA-262 v11, 20.4.1.11 * * @return constructed time in milliseconds */ ecma_number_t ecma_date_make_time (ecma_number_t hour, /**< hour value */ ecma_number_t min, /**< minute value */ ecma_number_t sec, /**< second value */ ecma_number_t ms) /**< millisecond value */ { if (!ecma_number_is_finite (hour) || !ecma_number_is_finite (min) || !ecma_number_is_finite (sec) || !ecma_number_is_finite (ms)) { return ecma_number_make_nan (); } ecma_number_t h = ecma_number_trunc (hour); ecma_number_t m = ecma_number_trunc (min); ecma_number_t s = ecma_number_trunc (sec); ecma_number_t milli = ecma_number_trunc (ms); return (ecma_number_t) ((h * ECMA_DATE_MS_PER_HOUR + m * ECMA_DATE_MS_PER_MINUTE + s * ECMA_DATE_MS_PER_SECOND + milli)); } /* ecma_date_make_time */ /** * Abstract operation: MakeDay * * See also: * ECMA-262 v11, 20.4.1.12 * * @return elpased number of days since Unix Epoch */ ecma_number_t ecma_date_make_day (ecma_number_t year, /**< year value */ ecma_number_t month, /**< month value */ ecma_number_t date) /**< date value */ { /* 1. */ if (!ecma_number_is_finite (year) || !ecma_number_is_finite (month) || !ecma_number_is_finite (date) || fabs (year) > INT32_MAX) { return ecma_number_make_nan (); } /* 2., 3., 4. */ int32_t y = (int32_t) (year); ecma_number_t m = ecma_number_trunc (month); ecma_number_t dt = ecma_number_trunc (date); /* 5. */ int32_t ym = y + (int32_t) (floor (m / 12)); /* 6. */ int32_t mn = (int32_t) fmod (m, 12); if (mn < 0) { mn += 12; } /* 7. */ ecma_number_t days = (ecma_date_day_from_year (ym) + first_day_in_month[ecma_date_in_leap_year (ym)][mn] + (dt - 1)); return days * ECMA_DATE_MS_PER_DAY; } /* ecma_date_make_day */ /** * Abstract operation: MakeTime * * See also: * ECMA-262 v11, 20.4.1.13 * * @return elpased number of milliceconds since Unix Epoch */ ecma_number_t ecma_date_make_date (ecma_number_t day, /**< day value */ ecma_number_t time) /**< time value */ { if (!ecma_number_is_finite (day) || !ecma_number_is_finite (time)) { return ecma_number_make_nan (); } return day + time; } /* ecma_date_make_date */ /** * Abstract operation: TimeClip * * See also: * ECMA-262 v11, 20.4.1.14 * * @return elpased number of milliceconds since Unix Epoch */ ecma_number_t ecma_date_time_clip (ecma_number_t time) /**< time value */ { if (!ecma_number_is_finite (time) || fabs (time) > ECMA_DATE_MAX_VALUE) { return ecma_number_make_nan (); } return ecma_number_trunc (time); } /* ecma_date_time_clip */ /** * Common function to convert date to string. * * @return ecma value * Returned value must be freed with ecma_free_value. */ static ecma_value_t ecma_date_to_string_format (ecma_number_t datetime_number, /**< datetime */ const char *format_p) /**< format buffer */ { const uint32_t date_buffer_length = 37; JERRY_VLA (lit_utf8_byte_t, date_buffer, date_buffer_length); lit_utf8_byte_t *dest_p = date_buffer; while (*format_p != LIT_CHAR_NULL) { if (*format_p != LIT_CHAR_DOLLAR_SIGN) { *dest_p++ = (lit_utf8_byte_t) *format_p++; continue; } format_p++; const char *str_p = NULL; int32_t number = 0; int32_t number_length = 0; switch (*format_p) { case LIT_CHAR_UPPERCASE_Y: /* Year. */ { number = ecma_date_year_from_time (datetime_number); if (number >= 100000 || number <= -100000) { number_length = 6; } else if (number >= 10000 || number <= -10000) { number_length = 5; } else { number_length = 4; } break; } case LIT_CHAR_LOWERCASE_Y: /* ISO Year: -000001, 0000, 0001, 9999, +012345 */ { number = ecma_date_year_from_time (datetime_number); if (0 <= number && number <= 9999) { number_length = 4; } else { number_length = 6; } break; } case LIT_CHAR_UPPERCASE_M: /* Month. */ { int32_t month = ecma_date_month_from_time (datetime_number); JERRY_ASSERT (month >= 0 && month <= 11); str_p = month_names_p[month]; break; } case LIT_CHAR_UPPERCASE_O: /* Month as number. */ { /* The 'ecma_date_month_from_time' (ECMA 262 v5, 15.9.1.4) returns a * number from 0 to 11, but we have to print the month from 1 to 12 * for ISO 8601 standard (ECMA 262 v5, 15.9.1.15). */ number = ecma_date_month_from_time (datetime_number) + 1; number_length = 2; break; } case LIT_CHAR_UPPERCASE_D: /* Day. */ { number = ecma_date_date_from_time (datetime_number); number_length = 2; break; } case LIT_CHAR_UPPERCASE_W: /* Day of week. */ { int32_t day = ecma_date_week_day (datetime_number); JERRY_ASSERT (day >= 0 && day <= 6); str_p = day_names_p[day]; break; } case LIT_CHAR_LOWERCASE_H: /* Hour. */ { number = ecma_date_hour_from_time (datetime_number); number_length = 2; break; } case LIT_CHAR_LOWERCASE_M: /* Minutes. */ { number = ecma_date_min_from_time (datetime_number); number_length = 2; break; } case LIT_CHAR_LOWERCASE_S: /* Seconds. */ { number = ecma_date_sec_from_time (datetime_number); number_length = 2; break; } case LIT_CHAR_LOWERCASE_I: /* Milliseconds. */ { number = ecma_date_ms_from_time (datetime_number); number_length = 3; break; } case LIT_CHAR_LOWERCASE_Z: /* Time zone hours part. */ { int32_t time_zone = (int32_t) ecma_date_local_time_zone_adjustment (datetime_number); if (time_zone >= 0) { *dest_p++ = LIT_CHAR_PLUS; } else { *dest_p++ = LIT_CHAR_MINUS; time_zone = -time_zone; } number = time_zone / ECMA_DATE_MS_PER_HOUR; number_length = 2; break; } default: { JERRY_ASSERT (*format_p == LIT_CHAR_UPPERCASE_Z); /* Time zone minutes part. */ int32_t time_zone = (int32_t) ecma_date_local_time_zone_adjustment (datetime_number); if (time_zone < 0) { time_zone = -time_zone; } number = (time_zone % ECMA_DATE_MS_PER_HOUR) / ECMA_DATE_MS_PER_MINUTE; number_length = 2; break; } } format_p++; if (str_p != NULL) { /* Print string values: month or day name which is always 3 characters */ memcpy (dest_p, str_p, 3); dest_p += 3; continue; } /* Print right aligned number values. */ JERRY_ASSERT (number_length > 0); if (number < 0) { number = -number; *dest_p++ = '-'; } else if (*(format_p - 1) == LIT_CHAR_LOWERCASE_Y && number_length == 6) { /* positive sign is compulsory for extended years */ *dest_p++ = '+'; } dest_p += number_length; lit_utf8_byte_t *buffer_p = dest_p; do { buffer_p--; *buffer_p = (lit_utf8_byte_t) ((number % 10) + (int32_t) LIT_CHAR_0); number /= 10; } while (--number_length); } JERRY_ASSERT (dest_p <= date_buffer + date_buffer_length); return ecma_make_string_value (ecma_new_ecma_string_from_utf8 (date_buffer, (lit_utf8_size_t) (dest_p - date_buffer))); } /* ecma_date_to_string_format */ /** * Common function to create a time zone specific string from a numeric value. * * Used by: * - The Date routine. * - The Date.prototype.toString routine. * * @return ecma value * Returned value must be freed with ecma_free_value. */ ecma_value_t ecma_date_value_to_string (ecma_number_t datetime_number) /**< datetime */ { datetime_number += ecma_date_local_time_zone_adjustment (datetime_number); return ecma_date_to_string_format (datetime_number, "$W $M $D $Y $h:$m:$s GMT$z$Z"); } /* ecma_date_value_to_string */ /** * Common function to create a time zone specific string from a numeric value. * * Used by: * - The Date.prototype.toUTCString routine. * * @return ecma value * Returned value must be freed with ecma_free_value. */ ecma_value_t ecma_date_value_to_utc_string (ecma_number_t datetime_number) /**< datetime */ { return ecma_date_to_string_format (datetime_number, "$W, $D $M $Y $h:$m:$s GMT"); } /* ecma_date_value_to_utc_string */ /** * Common function to create a ISO specific string from a numeric value. * * Used by: * - The Date.prototype.toISOString routine. * * @return ecma value * Returned value must be freed with ecma_free_value. */ ecma_value_t ecma_date_value_to_iso_string (ecma_number_t datetime_number) /**