#include // IWYU pragma: keep #include #include #include #include #include #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #include #include #else #include #endif #include "ant.h" #include "internal.h" #include "errors.h" #include "descriptors.h" #include "runtime.h" #include "silver/engine.h" #include "modules/date.h" #include "modules/symbol.h" static const int month_days[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; static const char month_names[] = "Jan" "Feb" "Mar" "Apr" "May" "Jun" "Jul" "Aug" "Sep" "Oct" "Nov" "Dec"; static const char day_names[] = "Sun" "Mon" "Tue" "Wed" "Thu" "Fri" "Sat"; static inline double *date_fields_slot(date_fields_t *f, date_field_index_t index) { switch (index) { case DATE_FIELD_YEAR: return &f->year; case DATE_FIELD_MONTH: return &f->month; case DATE_FIELD_DAY_OF_MONTH: return &f->day; case DATE_FIELD_HOUR: return &f->hour; case DATE_FIELD_MINUTE: return &f->minute; case DATE_FIELD_SECOND: return &f->second; case DATE_FIELD_MILLISECOND: return &f->millisecond; case DATE_FIELD_DAY_OF_WEEK: return &f->weekday; case DATE_FIELD_TZ_MINUTES: return &f->tz_minutes; default: return NULL; }} static inline double date_fields_get(const date_fields_t *f, date_field_index_t index) { double *slot = date_fields_slot((date_fields_t *)f, index); return slot ? *slot : 0.0; } static inline void date_fields_set(date_fields_t *f, date_field_index_t index, double value) { double *slot = date_fields_slot(f, index); if (slot) *slot = value; } static inline int date_min_int(int a, int b) { return (a < b) ? a : b; } static inline int to_upper_ascii(int c) { if (c >= 'a' && c <= 'z') return c - ('a' - 'A'); return c; } static inline bool date_is_primitive(ant_value_t v) { uint8_t t = vtype(v); return t == T_STR || t == T_NUM || t == T_BOOL || t == T_NULL || t == T_UNDEF || t == T_SYMBOL || t == T_BIGINT; } bool is_date_instance(ant_value_t value) { if (!is_object_type(value)) return false; ant_value_t brand = js_get_slot(js_as_obj(value), SLOT_BRAND); return vtype(brand) == T_NUM && (int)js_getnum(brand) == BRAND_DATE; } static bool date_this_time_value(ant_t *js, ant_value_t this_val, double *out, ant_value_t *err) { if (!is_date_instance(this_val)) { if (err) *err = js_mkerr_typed(js, JS_ERR_TYPE, "not a Date object"); return false; } ant_value_t t = js_get_slot(js_as_obj(this_val), SLOT_DATA); *out = (vtype(t) == T_NUM) ? tod(t) : JS_NAN; return true; } static ant_value_t date_set_this_time_value(ant_t *js, ant_value_t this_val, double v) { if (!is_date_instance(this_val)) { return js_mkerr_typed(js, JS_ERR_TYPE, "not a Date object"); } js_set_slot(js_as_obj(this_val), SLOT_DATA, tov(v)); return tov(v); } static int64_t math_mod(int64_t a, int64_t b) { int64_t m = a % b; return m + (m < 0) * b; } static int64_t floor_div_int64(int64_t a, int64_t b) { int64_t m = a % b; return (a - (m + (m < 0) * b)) / b; } static double date_timeclip(double t) { if (t >= -8.64e15 && t <= 8.64e15) return trunc(t) + 0.0; return JS_NAN; } static int64_t days_in_year(int64_t y) { return 365 + !(y % 4) - !(y % 100) + !(y % 400); } static int64_t days_from_year(int64_t y) { return 365 * (y - 1970) + floor_div_int64(y - 1969, 4) - floor_div_int64(y - 1901, 100) + floor_div_int64(y - 1601, 400); } static int64_t year_from_days(int64_t *days) { int64_t y, d1, nd, d = *days; y = floor_div_int64(d * 10000, 3652425) + 1970; while (true) { d1 = d - days_from_year(y); if (d1 < 0) { y--; d1 += days_in_year(y); continue; } nd = days_in_year(y); if (d1 < nd) break; d1 -= nd; y++; } *days = d1; return y; } static int get_timezone_offset(int64_t time_ms) { #ifdef _WIN32 DWORD r; TIME_ZONE_INFORMATION tzi; r = GetTimeZoneInformation(&tzi); if (r == TIME_ZONE_ID_INVALID) return 0; if (r == TIME_ZONE_ID_DAYLIGHT) return (int)(tzi.Bias + tzi.DaylightBias); return (int)tzi.Bias; #else time_t ti; struct tm tm_local; int64_t time_s = time_ms / 1000; if (sizeof(time_t) == 4) { if ((time_t)-1 < 0) { if (time_s < INT32_MIN) time_s = INT32_MIN; else if (time_s > INT32_MAX) time_s = INT32_MAX; } else { if (time_s < 0) time_s = 0; else if (time_s > UINT32_MAX) time_s = UINT32_MAX; } } ti = (time_t)time_s; #ifdef _WIN32 localtime_s(&tm_local, &ti); #else localtime_r(&ti, &tm_local); #endif #if defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__DragonFly__) || defined(__GLIBC__) return (int)(-tm_local.tm_gmtoff / 60); #else struct tm tm_gmt; #ifdef _WIN32 gmtime_s(&tm_gmt, &ti); #else gmtime_r(&ti, &tm_gmt); #endif tm_local.tm_isdst = 0; return (int)(difftime(mktime(&tm_gmt), mktime(&tm_local)) / 60); #endif #endif } static int date_extract_fields_from_time(double dval, date_fields_t *fields, int is_local, int force) { int64_t d, days, wd, y, i, md, h, m, s, ms, tz = 0; if (isnan(dval)) { if (!force) return 0; d = 0; } else { d = (int64_t)dval; if (is_local) { tz = -get_timezone_offset(d); d += tz * 60000; } } h = math_mod(d, 86400000); days = (d - h) / 86400000; ms = h % 1000; h = (h - ms) / 1000; s = h % 60; h = (h - s) / 60; m = h % 60; h = (h - m) / 60; wd = math_mod(days + 4, 7); y = year_from_days(&days); for (i = 0; i < 11; i++) { md = month_days[i]; if (i == 1) md += (int)days_in_year(y) - 365; if (days < md) break; days -= md; } fields->year = (double)y; fields->month = (double)i; fields->day = (double)(days + 1); fields->hour = (double)h; fields->minute = (double)m; fields->second = (double)s; fields->millisecond = (double)ms; fields->weekday = (double)wd; fields->tz_minutes = (double)tz; return 1; } static int date_extract_fields( ant_t *js, ant_value_t this_val, date_fields_t *fields, int is_local, int force, ant_value_t *err ) { double dval; if (!date_this_time_value(js, this_val, &dval, err)) return -1; return date_extract_fields_from_time(dval, fields, is_local, force); } static double date_make_fields(const date_fields_t *fields, int is_local) { double y, m, dt, ym, mn, day, h, s, milli, time, tv; int yi, mi, i; int64_t days; volatile double temp; double d = JS_NAN; y = fields->year; m = fields->month; dt = fields->day; ym = y + floor(m / 12); mn = fmod(m, 12); if (mn < 0) mn += 12; if (ym < -271821 || ym > 275760) return JS_NAN; yi = (int)ym; mi = (int)mn; days = days_from_year(yi); for (i = 0; i < mi; i++) { days += month_days[i]; if (i == 1) days += days_in_year(yi) - 365; } day = (double)days + dt - 1; h = fields->hour; m = fields->minute; s = fields->second; milli = fields->millisecond; time = h * 3600000; time += (temp = m * 60000); time += (temp = s * 1000); time += milli; tv = (temp = day * 86400000) + time; if (!isfinite(tv)) return JS_NAN; if (is_local) { int64_t ti; if (tv < (double)INT64_MIN) ti = INT64_MIN; else if (tv >= 0x1p63) ti = INT64_MAX; else ti = (int64_t)tv; tv += (double)get_timezone_offset(ti) * 60000.0; } d = date_timeclip(tv); return d; } ant_value_t get_date_string(ant_t *js, ant_value_t this_val, date_string_spec_t spec) { char buf[96]; date_fields_t fields; int res, fmt, pos; int y, mon, d, h, m, s, ms, wd, tz; ant_value_t err; fmt = (int)spec.fmt; res = date_extract_fields( js, this_val, &fields, spec.fmt == DATE_STRING_FMT_LOCAL, 0, &err ); if (res < 0) return err; if (!res) { if (spec.fmt == DATE_STRING_FMT_ISO) return js_mkerr_typed(js, JS_ERR_RANGE, "Date value is NaN"); return js_mkstr(js, "Invalid Date", 12); } y = (int)fields.year; mon = (int)fields.month; d = (int)fields.day; h = (int)fields.hour; m = (int)fields.minute; s = (int)fields.second; ms = (int)fields.millisecond; wd = (int)fields.weekday; tz = (int)fields.tz_minutes; pos = 0; if (spec.part & DATE_STRING_PART_DATE) { switch (fmt) { case 0: pos += snprintf( buf + pos, sizeof(buf) - (size_t)pos, "%.3s, %02d %.3s %0*d ", day_names + wd * 3, d, month_names + mon * 3, 4 + (y < 0), y); break; case 1: pos += snprintf( buf + pos, sizeof(buf) - (size_t)pos, "%.3s %.3s %02d %0*d", day_names + wd * 3, month_names + mon * 3, d, 4 + (y < 0), y); if (spec.part == DATE_STRING_PART_ALL) buf[pos++] = ' '; break; case 2: if (y >= 0 && y <= 9999) { pos += snprintf(buf + pos, sizeof(buf) - (size_t)pos, "%04d", y); } else pos += snprintf(buf + pos, sizeof(buf) - (size_t)pos, "%+07d", y); pos += snprintf(buf + pos, sizeof(buf) - (size_t)pos, "-%02d-%02dT", mon + 1, d); break; case 3: pos += snprintf( buf + pos, sizeof(buf) - (size_t)pos, "%02d/%02d/%0*d", mon + 1, d, 4 + (y < 0), y); if (spec.part == DATE_STRING_PART_ALL) { buf[pos++] = ','; buf[pos++] = ' '; } break; default: break; } } if (spec.part & DATE_STRING_PART_TIME) { switch (fmt) { case 0: pos += snprintf(buf + pos, sizeof(buf) - (size_t)pos, "%02d:%02d:%02d GMT", h, m, s); break; case 1: pos += snprintf(buf + pos, sizeof(buf) - (size_t)pos, "%02d:%02d:%02d GMT", h, m, s); if (tz < 0) { buf[pos++] = '-'; tz = -tz; } else buf[pos++] = '+'; pos += snprintf(buf + pos, sizeof(buf) - (size_t)pos, "%02d%02d", tz / 60, tz % 60); break; case 2: pos += snprintf(buf + pos, sizeof(buf) - (size_t)pos, "%02d:%02d:%02d.%03dZ", h, m, s, ms); break; case 3: pos += snprintf( buf + pos, sizeof(buf) - (size_t)pos, "%02d:%02d:%02d %cM", (h + 11) % 12 + 1, m, s, (h < 12) ? 'A' : 'P'); break; default: break; } } if (pos <= 0) return js_mkstr(js, "", 0); return js_mkstr(js, buf, (size_t)pos); } static int64_t date_now(void) { struct timeval tv; gettimeofday(&tv, NULL); return (int64_t)tv.tv_sec * 1000 + (int64_t)(tv.tv_usec / 1000); } static bool string_skip_char(const uint8_t *sp, int *pp, int c) { if (sp[*pp] == c) { *pp += 1; return true; } return false; } static int string_skip_spaces(const uint8_t *sp, int *pp) { int c; while ((c = sp[*pp]) == ' ') *pp += 1; return c; } static int string_skip_separators(const uint8_t *sp, int *pp) { int c; while ((c = sp[*pp]) == '-' || c == '/' || c == '.' || c == ',') *pp += 1; return c; } static int string_skip_until(const uint8_t *sp, int *pp, const char *stoplist) { int c; while (!strchr(stoplist, c = sp[*pp])) *pp += 1; return c; } static bool string_get_digits(const uint8_t *sp, int *pp, int *pval, int min_digits, int max_digits) { int v = 0; int c; int p = *pp; int p_start = p; while ((c = sp[p]) >= '0' && c <= '9') { if (v >= 100000000) return false; v = v * 10 + c - '0'; p++; if (max_digits > 0 && p - p_start == max_digits) break; } if (p - p_start < min_digits) return false; *pval = v; *pp = p; return true; } static bool string_get_milliseconds(const uint8_t *sp, int *pp, int *pval) { int mul = 100; int ms = 0; int c; int p_start; int p = *pp; c = sp[p]; if (c == '.' || c == ',') { p++; p_start = p; while ((c = sp[p]) >= '0' && c <= '9') { ms += (c - '0') * mul; mul /= 10; p++; if (p - p_start == 9) break; } if (p > p_start) { *pval = ms; *pp = p; } } return true; } static bool string_get_tzoffset(const uint8_t *sp, int *pp, int *tzp, bool strict) { int tz = 0; int p = *pp; int sgn; int hh; int mm; sgn = sp[p++]; if (sgn == '+' || sgn == '-') { int n = p; if (!string_get_digits(sp, &p, &hh, 1, 0)) return false; n = p - n; if (strict && n != 2 && n != 4) return false; while (n > 4) { n -= 2; hh /= 100; } if (n > 2) { mm = hh % 100; hh = hh / 100; } else { mm = 0; if (string_skip_char(sp, &p, ':') && !string_get_digits(sp, &p, &mm, 2, 2)) return false; } if (hh > 23 || mm > 59) return false; tz = hh * 60 + mm; if (sgn != '+') tz = -tz; } else if (sgn != 'Z') return false; *pp = p; *tzp = tz; return true; } static bool string_match(const uint8_t *sp, int *pp, const char *s) { int p = *pp; while (*s != '\0') { if (to_upper_ascii(sp[p]) != to_upper_ascii(*s++)) return false; p++; } *pp = p; return true; } static int find_abbrev(const uint8_t *sp, int p, const char *list, int count) { int n; int i; for (n = 0; n < count; n++) { for (i = 0;; i++) { if (to_upper_ascii(sp[p + i]) != to_upper_ascii(list[n * 3 + i])) break; if (i == 2) return n; } } return -1; } static bool string_get_month(const uint8_t *sp, int *pp, int *pval) { int n = find_abbrev(sp, *pp, month_names, 12); if (n < 0) return false; *pval = n + 1; *pp += 3; return true; } static bool parse_isostring(const uint8_t *sp, int fields[9], bool *is_local) { int sgn; int i; int p = 0; for (i = 0; i < 9; i++) fields[i] = (i == 2); *is_local = false; sgn = sp[p]; if (sgn == '-' || sgn == '+') { p++; if (!string_get_digits(sp, &p, &fields[0], 6, 6)) return false; if (sgn == '-') { if (fields[0] == 0) return false; fields[0] = -fields[0]; } } else { if (!string_get_digits(sp, &p, &fields[0], 4, 4)) return false; } if (string_skip_char(sp, &p, '-')) { if (!string_get_digits(sp, &p, &fields[1], 2, 2)) return false; if (fields[1] < 1) return false; fields[1] -= 1; if (string_skip_char(sp, &p, '-')) { if (!string_get_digits(sp, &p, &fields[2], 2, 2)) return false; if (fields[2] < 1) return false; } } if (string_skip_char(sp, &p, 'T')) { *is_local = true; if (!string_get_digits(sp, &p, &fields[3], 2, 2) || !string_skip_char(sp, &p, ':') || !string_get_digits(sp, &p, &fields[4], 2, 2)) { fields[3] = 100; return true; } if (string_skip_char(sp, &p, ':')) { if (!string_get_digits(sp, &p, &fields[5], 2, 2)) return false; string_get_milliseconds(sp, &p, &fields[6]); } } if (sp[p]) { *is_local = false; if (!string_get_tzoffset(sp, &p, &fields[8], true)) return false; } return sp[p] == '\0'; } typedef struct { char name[6]; int16_t offset; } tzabbr_t; static const tzabbr_t js_tzabbr[] = { {"GMT", 0}, {"UTC", 0}, {"UT", 0}, {"Z", 0}, {"EDT", -4 * 60}, {"EST", -5 * 60}, {"CDT", -5 * 60}, {"CST", -6 * 60}, {"MDT", -6 * 60}, {"MST", -7 * 60}, {"PDT", -7 * 60}, {"PST", -8 * 60}, {"WET", 0}, {"WEST", +1 * 60}, {"CET", +1 * 60}, {"CEST", +2 * 60}, {"EET", +2 * 60}, {"EEST", +3 * 60}, }; static bool string_get_tzabbr(const uint8_t *sp, int *pp, int *offset) { for (int i = 0; i < DATE_COUNT_OF(js_tzabbr); i++) { if (string_match(sp, pp, js_tzabbr[i].name)) { *offset = js_tzabbr[i].offset; return true; } } return false; } static bool parse_otherstring(const uint8_t *sp, int fields[9], bool *is_local) { int c; int i; int val; int p = 0; int p_start; int num[3]; bool has_year = false; bool has_mon = false; bool has_time = false; int num_index = 0; fields[0] = 2001; fields[1] = 1; fields[2] = 1; for (i = 3; i < 9; i++) fields[i] = 0; *is_local = true; while (string_skip_spaces(sp, &p)) { p_start = p; if ((c = sp[p]) == '+' || c == '-') { if (has_time && string_get_tzoffset(sp, &p, &fields[8], false)) { *is_local = false; } else { p++; if (string_get_digits(sp, &p, &val, 1, 0)) { if (c == '-') { if (val == 0) return false; val = -val; } fields[0] = val; has_year = true; } } } else if (string_get_digits(sp, &p, &val, 1, 0)) { if (string_skip_char(sp, &p, ':')) { fields[3] = val; if (!string_get_digits(sp, &p, &fields[4], 1, 2)) return false; if (string_skip_char(sp, &p, ':')) { if (!string_get_digits(sp, &p, &fields[5], 1, 2)) return false; string_get_milliseconds(sp, &p, &fields[6]); } else if (sp[p] != '\0' && sp[p] != ' ') return false; has_time = true; } else { if (p - p_start > 2) { fields[0] = val; has_year = true; } else if (val < 1 || val > 31) { fields[0] = val + (val < 100) * 1900 + (val < 50) * 100; has_year = true; } else { if (num_index == 3) return false; num[num_index++] = val; } } } else if (string_get_month(sp, &p, &fields[1])) { has_mon = true; string_skip_until(sp, &p, "0123456789 -/("); } else if (has_time && string_match(sp, &p, "PM")) { if (fields[3] != 12) fields[3] += 12; continue; } else if (has_time && string_match(sp, &p, "AM")) { if (fields[3] > 12) return false; if (fields[3] == 12) fields[3] -= 12; continue; } else if (string_get_tzabbr(sp, &p, &fields[8])) { *is_local = false; continue; } else if (c == '(') { int level = 0; while ((c = sp[p]) != '\0') { p++; level += (c == '('); level -= (c == ')'); if (!level) break; } if (level > 0) return false; } else if (c == ')') { return false; } else { if ((int)has_year + (int)has_mon + (int)has_time + num_index) return false; string_skip_until(sp, &p, " -/("); } string_skip_separators(sp, &p); } if (num_index + (int)has_year + (int)has_mon > 3) return false; switch (num_index) { case 0: if (!has_year) return false; break; case 1: if (has_mon) fields[2] = num[0]; else fields[1] = num[0]; break; case 2: if (has_year) { fields[1] = num[0]; fields[2] = num[1]; } else if (has_mon) { fields[0] = num[1] + (num[1] < 100) * 1900 + (num[1] < 50) * 100; fields[2] = num[0]; } else { fields[1] = num[0]; fields[2] = num[1]; } break; case 3: fields[0] = num[2] + (num[2] < 100) * 1900 + (num[2] < 50) * 100; fields[1] = num[0]; fields[2] = num[1]; break; default: return false; } if (fields[1] < 1 || fields[2] < 1) return false; fields[1] -= 1; return true; } static bool date_parse_string_to_ms(ant_t *js, ant_value_t arg, double *out_ms) { ant_value_t s = coerce_to_str(js, arg); if (is_err(s)) return false; ant_offset_t len = 0; ant_offset_t off = vstr(js, s, &len); char *buf = (char *)malloc((size_t)len + 1); if (!buf) { *out_ms = JS_NAN; return true; } memcpy(buf, (const void *)(uintptr_t)off, (size_t)len); buf[len] = '\0'; int fields[9]; date_fields_t fields1 = {0}; bool is_local = false; bool ok = parse_isostring((const uint8_t *)buf, fields, &is_local) || parse_otherstring((const uint8_t *)buf, fields, &is_local); free(buf); if (!ok) { *out_ms = JS_NAN; return true; } static const int field_max[6] = {0, 11, 31, 24, 59, 59}; bool valid = true; for (int i = 1; i < 6; i++) { if (fields[i] > field_max[i]) valid = false; } if (fields[3] == 24 && (fields[4] | fields[5] | fields[6])) valid = false; if (!valid) { *out_ms = JS_NAN; return true; } fields1.year = (double)fields[0]; fields1.month = (double)fields[1]; fields1.day = (double)fields[2]; fields1.hour = (double)fields[3]; fields1.minute = (double)fields[4]; fields1.second = (double)fields[5]; fields1.millisecond = (double)fields[6]; *out_ms = date_make_fields(&fields1, is_local ? 1 : 0) - (double)fields[8] * 60000.0; return true; } static ant_value_t builtin_Date(ant_t *js, ant_value_t *args, int nargs) { double val; static const date_string_spec_t kDateToStringSpec = { DATE_STRING_FMT_LOCAL, DATE_STRING_PART_ALL }; if (vtype(js->new_target) == T_UNDEF) { val = (double)date_now(); ant_value_t tmp = js_mkobj(js); ant_value_t date_proto = js_get_ctor_proto(js, "Date", 4); if (is_object_type(date_proto)) js_set_proto_init(tmp, date_proto); js_set_slot(tmp, SLOT_DATA, tov(val)); js_set_slot(tmp, SLOT_BRAND, js_mknum(BRAND_DATE)); return get_date_string(js, tmp, kDateToStringSpec); } if (nargs == 0) { val = (double)date_now(); } else if (nargs == 1) { ant_value_t input = args[0]; if (is_object_type(input) && is_date_instance(input)) { ant_value_t tv = js_get_slot(js_as_obj(input), SLOT_DATA); val = (vtype(tv) == T_NUM) ? tod(tv) : JS_NAN; val = date_timeclip(val); } else { ant_value_t prim = js_to_primitive(js, input, 0); if (is_err(prim)) return prim; if (vtype(prim) == T_STR) { if (!date_parse_string_to_ms(js, prim, &val)) return js_mkerr_typed(js, JS_ERR_INTERNAL, "Date parse failed"); } else val = js_to_number(js, prim); val = date_timeclip(val); } } else { date_fields_t fields = {0}; fields.day = 1; int n = nargs; if (n > 7) n = 7; int i; for (i = 0; i < n; i++) { double a = js_to_number(js, args[i]); if (!isfinite(a)) break; double t = trunc(a); date_fields_set(&fields, (date_field_index_t)i, t); if (i == 0 && fields.year >= 0 && fields.year < 100) fields.year += 1900; } val = (i == n) ? date_make_fields(&fields, 1) : JS_NAN; } js_set_slot(js_as_obj(js->this_val), SLOT_DATA, tov(val)); js_set_slot(js_as_obj(js->this_val), SLOT_BRAND, js_mknum(BRAND_DATE)); return js->this_val; } static ant_value_t builtin_Date_UTC(ant_t *js, ant_value_t *args, int nargs) { if (nargs == 0) return tov(JS_NAN); date_fields_t fields = {0}; fields.day = 1; int n = nargs; if (n > 7) n = 7; for (int i = 0; i < n; i++) { double a = js_to_number(js, args[i]); if (!isfinite(a)) return tov(JS_NAN); double t = trunc(a); date_fields_set(&fields, (date_field_index_t)i, t); if (i == 0 && fields.year >= 0 && fields.year < 100) fields.year += 1900; } return tov(date_make_fields(&fields, 0)); } static ant_value_t builtin_Date_parse(ant_t *js, ant_value_t *args, int nargs) { if (nargs < 1) return tov(JS_NAN); double v; if (!date_parse_string_to_ms(js, args[0], &v)) { return js_mkerr_typed(js, JS_ERR_INTERNAL, "Date parse failed"); } return tov(v); } static ant_value_t builtin_Date_now(ant_t *js, ant_value_t *args, int nargs) { return tov((double)date_now()); } static ant_value_t date_get_field(ant_t *js, date_get_field_spec_t spec) { date_fields_t fields; ant_value_t err; int res = date_extract_fields(js, js->this_val, &fields, spec.local_time, 0, &err); if (res < 0) return err; if (!res) return tov(JS_NAN); if (spec.legacy_get_year) fields.year -= 1900; return tov(date_fields_get(&fields, spec.field)); } static ant_value_t date_set_field(ant_t *js, ant_value_t *args, int nargs, date_set_field_spec_t spec) { date_fields_t fields; ant_value_t err; double d = JS_NAN; int res = date_extract_fields( js, js->this_val, &fields, spec.local_time, spec.first_field == DATE_FIELD_YEAR, &err ); if (res < 0) return err; int n = date_min_int(nargs, spec.end_field_exclusive - spec.first_field); for (int i = 0; i < n; i++) { double a = js_to_number(js, args[i]); if (!isfinite(a)) return date_set_this_time_value(js, js->this_val, JS_NAN); date_fields_set(&fields, (date_field_index_t)(spec.first_field + i), trunc(a)); } if (!res) return tov(JS_NAN); if (nargs > 0) d = date_make_fields(&fields, spec.local_time); return date_set_this_time_value(js, js->this_val, d); } static ant_value_t builtin_Date_valueOf(ant_t *js, ant_value_t *args, int nargs) { double v; ant_value_t err; if (!date_this_time_value(js, js->this_val, &v, &err)) return err; return tov(v); } static ant_value_t builtin_Date_getTime(ant_t *js, ant_value_t *args, int nargs) { return builtin_Date_valueOf(js, args, nargs); } static ant_value_t builtin_Date_toUTCString(ant_t *js, ant_value_t *args, int nargs) { static const date_string_spec_t kSpec = {DATE_STRING_FMT_UTC, DATE_STRING_PART_ALL}; return get_date_string(js, js->this_val, kSpec); } static ant_value_t builtin_Date_toString(ant_t *js, ant_value_t *args, int nargs) { static const date_string_spec_t kSpec = {DATE_STRING_FMT_LOCAL, DATE_STRING_PART_ALL}; return get_date_string(js, js->this_val, kSpec); } static ant_value_t builtin_Date_toISOString(ant_t *js, ant_value_t *args, int nargs) { static const date_string_spec_t kSpec = {DATE_STRING_FMT_ISO, DATE_STRING_PART_ALL}; return get_date_string(js, js->this_val, kSpec); } static ant_value_t builtin_Date_toDateString(ant_t *js, ant_value_t *args, int nargs) { static const date_string_spec_t kSpec = {DATE_STRING_FMT_LOCAL, DATE_STRING_PART_DATE}; return get_date_string(js, js->this_val, kSpec); } static ant_value_t builtin_Date_toTimeString(ant_t *js, ant_value_t *args, int nargs) { static const date_string_spec_t kSpec = {DATE_STRING_FMT_LOCAL, DATE_STRING_PART_TIME}; return get_date_string(js, js->this_val, kSpec); } static ant_value_t builtin_Date_toLocaleString(ant_t *js, ant_value_t *args, int nargs) { static const date_string_spec_t kSpec = {DATE_STRING_FMT_LOCALE, DATE_STRING_PART_ALL}; return get_date_string(js, js->this_val, kSpec); } static ant_value_t builtin_Date_toLocaleDateString(ant_t *js, ant_value_t *args, int nargs) { static const date_string_spec_t kSpec = {DATE_STRING_FMT_LOCALE, DATE_STRING_PART_DATE}; return get_date_string(js, js->this_val, kSpec); } static ant_value_t builtin_Date_toLocaleTimeString(ant_t *js, ant_value_t *args, int nargs) { static const date_string_spec_t kSpec = {DATE_STRING_FMT_LOCALE, DATE_STRING_PART_TIME}; return get_date_string(js, js->this_val, kSpec); } static ant_value_t builtin_Date_getTimezoneOffset(ant_t *js, ant_value_t *args, int nargs) { double v; ant_value_t err; if (!date_this_time_value(js, js->this_val, &v, &err)) return err; if (isnan(v)) return tov(JS_NAN); return tov((double)get_timezone_offset((int64_t)trunc(v))); } static ant_value_t builtin_Date_setTime(ant_t *js, ant_value_t *args, int nargs) { double cur; ant_value_t err; if (!date_this_time_value(js, js->this_val, &cur, &err)) return err; double v = (nargs > 0) ? js_to_number(js, args[0]) : JS_NAN; return date_set_this_time_value(js, js->this_val, date_timeclip(v)); } static ant_value_t builtin_Date_getYear(ant_t *js, ant_value_t *args, int nargs) { static const date_get_field_spec_t kSpec = {DATE_FIELD_YEAR, true, true}; return date_get_field(js, kSpec); } static ant_value_t builtin_Date_getFullYear(ant_t *js, ant_value_t *args, int nargs) { static const date_get_field_spec_t kSpec = {DATE_FIELD_YEAR, true, false}; return date_get_field(js, kSpec); } static ant_value_t builtin_Date_getUTCFullYear(ant_t *js, ant_value_t *args, int nargs) { static const date_get_field_spec_t kSpec = {DATE_FIELD_YEAR, false, false}; return date_get_field(js, kSpec); } static ant_value_t builtin_Date_getMonth(ant_t *js, ant_value_t *args, int nargs) { static const date_get_field_spec_t kSpec = {DATE_FIELD_MONTH, true, false}; return date_get_field(js, kSpec); } static ant_value_t builtin_Date_getUTCMonth(ant_t *js, ant_value_t *args, int nargs) { static const date_get_field_spec_t kSpec = {DATE_FIELD_MONTH, false, false}; return date_get_field(js, kSpec); } static ant_value_t builtin_Date_getDate(ant_t *js, ant_value_t *args, int nargs) { static const date_get_field_spec_t kSpec = {DATE_FIELD_DAY_OF_MONTH, true, false}; return date_get_field(js, kSpec); } static ant_value_t builtin_Date_getUTCDate(ant_t *js, ant_value_t *args, int nargs) { static const date_get_field_spec_t kSpec = {DATE_FIELD_DAY_OF_MONTH, false, false}; return date_get_field(js, kSpec); } static ant_value_t builtin_Date_getHours(ant_t *js, ant_value_t *args, int nargs) { static const date_get_field_spec_t kSpec = {DATE_FIELD_HOUR, true, false}; return date_get_field(js, kSpec); } static ant_value_t builtin_Date_getUTCHours(ant_t *js, ant_value_t *args, int nargs) { static const date_get_field_spec_t kSpec = {DATE_FIELD_HOUR, false, false}; return date_get_field(js, kSpec); } static ant_value_t builtin_Date_getMinutes(ant_t *js, ant_value_t *args, int nargs) { static const date_get_field_spec_t kSpec = {DATE_FIELD_MINUTE, true, false}; return date_get_field(js, kSpec); } static ant_value_t builtin_Date_getUTCMinutes(ant_t *js, ant_value_t *args, int nargs) { static const date_get_field_spec_t kSpec = {DATE_FIELD_MINUTE, false, false}; return date_get_field(js, kSpec); } static ant_value_t builtin_Date_getSeconds(ant_t *js, ant_value_t *args, int nargs) { static const date_get_field_spec_t kSpec = {DATE_FIELD_SECOND, true, false}; return date_get_field(js, kSpec); } static ant_value_t builtin_Date_getUTCSeconds(ant_t *js, ant_value_t *args, int nargs) { static const date_get_field_spec_t kSpec = {DATE_FIELD_SECOND, false, false}; return date_get_field(js, kSpec); } static ant_value_t builtin_Date_getMilliseconds(ant_t *js, ant_value_t *args, int nargs) { static const date_get_field_spec_t kSpec = {DATE_FIELD_MILLISECOND, true, false}; return date_get_field(js, kSpec); } static ant_value_t builtin_Date_getUTCMilliseconds(ant_t *js, ant_value_t *args, int nargs) { static const date_get_field_spec_t kSpec = {DATE_FIELD_MILLISECOND, false, false}; return date_get_field(js, kSpec); } static ant_value_t builtin_Date_getDay(ant_t *js, ant_value_t *args, int nargs) { static const date_get_field_spec_t kSpec = {DATE_FIELD_DAY_OF_WEEK, true, false}; return date_get_field(js, kSpec); } static ant_value_t builtin_Date_getUTCDay(ant_t *js, ant_value_t *args, int nargs) { static const date_get_field_spec_t kSpec = {DATE_FIELD_DAY_OF_WEEK, false, false}; return date_get_field(js, kSpec); } static ant_value_t builtin_Date_setMilliseconds(ant_t *js, ant_value_t *args, int nargs) { static const date_set_field_spec_t kSpec = {DATE_FIELD_MILLISECOND, DATE_FIELD_DAY_OF_WEEK, true}; return date_set_field(js, args, nargs, kSpec); } static ant_value_t builtin_Date_setUTCMilliseconds(ant_t *js, ant_value_t *args, int nargs) { static const date_set_field_spec_t kSpec = {DATE_FIELD_MILLISECOND, DATE_FIELD_DAY_OF_WEEK, false}; return date_set_field(js, args, nargs, kSpec); } static ant_value_t builtin_Date_setSeconds(ant_t *js, ant_value_t *args, int nargs) { static const date_set_field_spec_t kSpec = {DATE_FIELD_SECOND, DATE_FIELD_DAY_OF_WEEK, true}; return date_set_field(js, args, nargs, kSpec); } static ant_value_t builtin_Date_setUTCSeconds(ant_t *js, ant_value_t *args, int nargs) { static const date_set_field_spec_t kSpec = {DATE_FIELD_SECOND, DATE_FIELD_DAY_OF_WEEK, false}; return date_set_field(js, args, nargs, kSpec); } static ant_value_t builtin_Date_setMinutes(ant_t *js, ant_value_t *args, int nargs) { static const date_set_field_spec_t kSpec = {DATE_FIELD_MINUTE, DATE_FIELD_DAY_OF_WEEK, true}; return date_set_field(js, args, nargs, kSpec); } static ant_value_t builtin_Date_setUTCMinutes(ant_t *js, ant_value_t *args, int nargs) { static const date_set_field_spec_t kSpec = {DATE_FIELD_MINUTE, DATE_FIELD_DAY_OF_WEEK, false}; return date_set_field(js, args, nargs, kSpec); } static ant_value_t builtin_Date_setHours(ant_t *js, ant_value_t *args, int nargs) { static const date_set_field_spec_t kSpec = {DATE_FIELD_HOUR, DATE_FIELD_DAY_OF_WEEK, true}; return date_set_field(js, args, nargs, kSpec); } static ant_value_t builtin_Date_setUTCHours(ant_t *js, ant_value_t *args, int nargs) { static const date_set_field_spec_t kSpec = {DATE_FIELD_HOUR, DATE_FIELD_DAY_OF_WEEK, false}; return date_set_field(js, args, nargs, kSpec); } static ant_value_t builtin_Date_setDate(ant_t *js, ant_value_t *args, int nargs) { static const date_set_field_spec_t kSpec = {DATE_FIELD_DAY_OF_MONTH, DATE_FIELD_HOUR, true}; return date_set_field(js, args, nargs, kSpec); } static ant_value_t builtin_Date_setUTCDate(ant_t *js, ant_value_t *args, int nargs) { static const date_set_field_spec_t kSpec = {DATE_FIELD_DAY_OF_MONTH, DATE_FIELD_HOUR, false}; return date_set_field(js, args, nargs, kSpec); } static ant_value_t builtin_Date_setMonth(ant_t *js, ant_value_t *args, int nargs) { static const date_set_field_spec_t kSpec = {DATE_FIELD_MONTH, DATE_FIELD_DAY_OF_MONTH, true}; return date_set_field(js, args, nargs, kSpec); } static ant_value_t builtin_Date_setUTCMonth(ant_t *js, ant_value_t *args, int nargs) { static const date_set_field_spec_t kSpec = {DATE_FIELD_MONTH, DATE_FIELD_DAY_OF_MONTH, false}; return date_set_field(js, args, nargs, kSpec); } static ant_value_t builtin_Date_setFullYear(ant_t *js, ant_value_t *args, int nargs) { static const date_set_field_spec_t kSpec = {DATE_FIELD_YEAR, DATE_FIELD_HOUR, true}; return date_set_field(js, args, nargs, kSpec); } static ant_value_t builtin_Date_setUTCFullYear(ant_t *js, ant_value_t *args, int nargs) { static const date_set_field_spec_t kSpec = {DATE_FIELD_YEAR, DATE_FIELD_HOUR, false}; return date_set_field(js, args, nargs, kSpec); } static ant_value_t builtin_Date_setYear(ant_t *js, ant_value_t *args, int nargs) { double y; ant_value_t err; if (!date_this_time_value(js, js->this_val, &y, &err)) return err; y = (nargs > 0) ? js_to_number(js, args[0]) : JS_NAN; if (isnan(y)) return date_set_this_time_value(js, js->this_val, y); if (isfinite(y)) { y = trunc(y); if (y >= 0 && y < 100) y += 1900; } ant_value_t darg = tov(y); static const date_set_field_spec_t kSetYearSpec = { DATE_FIELD_YEAR, DATE_FIELD_MONTH, true }; return date_set_field(js, &darg, 1, kSetYearSpec); } static ant_value_t date_to_primitive_number(ant_t *js, ant_value_t obj) { ant_value_t to_primitive_sym = get_toPrimitive_sym(); if (vtype(to_primitive_sym) == T_SYMBOL) { ant_value_t ex = js_get_sym(js, obj, to_primitive_sym); uint8_t et = vtype(ex); if (et == T_FUNC || et == T_CFUNC) { ant_value_t hint = js_mkstr(js, "number", 6); ant_value_t result = sv_vm_call(js->vm, js, ex, obj, &hint, 1, NULL, false); if (is_err(result)) return result; if (date_is_primitive(result)) return result; return js_mkerr_typed(js, JS_ERR_TYPE, "Cannot convert object to primitive value"); } if (et != T_UNDEF) { return js_mkerr_typed(js, JS_ERR_TYPE, "Symbol.toPrimitive is not a function"); } } const char *methods[2] = {"valueOf", "toString"}; for (int i = 0; i < 2; i++) { ant_value_t method = js_getprop_fallback(js, obj, methods[i]); uint8_t mt = vtype(method); if (mt == T_FUNC || mt == T_CFUNC) { ant_value_t result = sv_vm_call(js->vm, js, method, obj, NULL, 0, NULL, false); if (is_err(result)) return result; if (date_is_primitive(result)) return result; } } return js_mkerr_typed(js, JS_ERR_TYPE, "Cannot convert object to primitive value"); } static ant_value_t builtin_Date_toJSON(ant_t *js, ant_value_t *args, int nargs) { ant_value_t obj = js->this_val; if (!is_object_type(obj)) { return js_mkerr_typed(js, JS_ERR_TYPE, "Date.prototype.toJSON called on non-object"); } ant_value_t tv = date_to_primitive_number(js, obj); if (is_err(tv)) return tv; if (vtype(tv) == T_NUM && !isfinite(tod(tv))) { return js_mknull(); } ant_value_t method = js_getprop_fallback(js, obj, "toISOString"); uint8_t mt = vtype(method); if (mt != T_FUNC && mt != T_CFUNC) { return js_mkerr_typed(js, JS_ERR_TYPE, "object needs toISOString method"); } return sv_vm_call(js->vm, js, method, obj, NULL, 0, NULL, false); } static ant_value_t date_toPrimitive(ant_t *js, ant_value_t *args, int nargs) { ant_value_t this_val = js_getthis(js); if (!is_object_type(this_val)) { return js_mkerr_typed(js, JS_ERR_TYPE, "Not an object"); } int hint_num; if (nargs > 0 && vtype(args[0]) == T_STR) { size_t hint_len = 0; const char *hint = js_getstr(js, args[0], &hint_len); if (!hint) return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid hint"); if ((hint_len == 6 && memcmp(hint, "number", 6) == 0) || (hint_len == 7 && memcmp(hint, "integer", 7) == 0)) { hint_num = 1; } else if ( (hint_len == 6 && memcmp(hint, "string", 6) == 0) || (hint_len == 7 && memcmp(hint, "default", 7) == 0)) { hint_num = 0; } else { return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid hint"); } } else return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid hint"); const char *methods0[2] = {"toString", "valueOf"}; const char *methods1[2] = {"valueOf", "toString"}; const char **methods = hint_num ? methods1 : methods0; for (int i = 0; i < 2; i++) { ant_value_t method = js_getprop_fallback(js, this_val, methods[i]); uint8_t mt = vtype(method); if (mt == T_FUNC || mt == T_CFUNC) { ant_value_t result = sv_vm_call(js->vm, js, method, this_val, NULL, 0, NULL, false); if (is_err(result)) return result; if (date_is_primitive(result)) return result; } } return js_mkerr_typed(js, JS_ERR_TYPE, "Cannot convert object to primitive value"); } static void date_define_methods( ant_t *js, ant_value_t target, const date_method_entry_t *methods, size_t count ) { for (size_t i = 0; i < count; i++) js_setprop(js,target, js_mkstr(js, methods[i].name, methods[i].len), js_mkfun_dyn(methods[i].fn) ); } void init_date_module(void) { ant_t *js = rt->js; ant_value_t glob = js->global; ant_value_t object_proto = js->sym.object_proto; ant_value_t function_proto = js_get_ctor_proto(js, "Function", 8); ant_value_t date_proto = js_mkobj(js); js_set_proto_init(date_proto, object_proto); static const date_method_entry_t kDateProtoMethods[] = { DATE_METHOD_ENTRY("valueOf", builtin_Date_valueOf), DATE_METHOD_ENTRY("toString", builtin_Date_toString), DATE_METHOD_ENTRY("toUTCString", builtin_Date_toUTCString), DATE_METHOD_ENTRY("toGMTString", builtin_Date_toUTCString), DATE_METHOD_ENTRY("toISOString", builtin_Date_toISOString), DATE_METHOD_ENTRY("toDateString", builtin_Date_toDateString), DATE_METHOD_ENTRY("toTimeString", builtin_Date_toTimeString), DATE_METHOD_ENTRY("toLocaleString", builtin_Date_toLocaleString), DATE_METHOD_ENTRY("toLocaleDateString", builtin_Date_toLocaleDateString), DATE_METHOD_ENTRY("toLocaleTimeString", builtin_Date_toLocaleTimeString), DATE_METHOD_ENTRY("getTimezoneOffset", builtin_Date_getTimezoneOffset), DATE_METHOD_ENTRY("getTime", builtin_Date_getTime), DATE_METHOD_ENTRY("getYear", builtin_Date_getYear), DATE_METHOD_ENTRY("getFullYear", builtin_Date_getFullYear), DATE_METHOD_ENTRY("getUTCFullYear", builtin_Date_getUTCFullYear), DATE_METHOD_ENTRY("getMonth", builtin_Date_getMonth), DATE_METHOD_ENTRY("getUTCMonth", builtin_Date_getUTCMonth), DATE_METHOD_ENTRY("getDate", builtin_Date_getDate), DATE_METHOD_ENTRY("getUTCDate", builtin_Date_getUTCDate), DATE_METHOD_ENTRY("getHours", builtin_Date_getHours), DATE_METHOD_ENTRY("getUTCHours", builtin_Date_getUTCHours), DATE_METHOD_ENTRY("getMinutes", builtin_Date_getMinutes), DATE_METHOD_ENTRY("getUTCMinutes", builtin_Date_getUTCMinutes), DATE_METHOD_ENTRY("getSeconds", builtin_Date_getSeconds), DATE_METHOD_ENTRY("getUTCSeconds", builtin_Date_getUTCSeconds), DATE_METHOD_ENTRY("getMilliseconds", builtin_Date_getMilliseconds), DATE_METHOD_ENTRY("getUTCMilliseconds", builtin_Date_getUTCMilliseconds), DATE_METHOD_ENTRY("getDay", builtin_Date_getDay), DATE_METHOD_ENTRY("getUTCDay", builtin_Date_getUTCDay), DATE_METHOD_ENTRY("setTime", builtin_Date_setTime), DATE_METHOD_ENTRY("setMilliseconds", builtin_Date_setMilliseconds), DATE_METHOD_ENTRY("setUTCMilliseconds", builtin_Date_setUTCMilliseconds), DATE_METHOD_ENTRY("setSeconds", builtin_Date_setSeconds), DATE_METHOD_ENTRY("setUTCSeconds", builtin_Date_setUTCSeconds), DATE_METHOD_ENTRY("setMinutes", builtin_Date_setMinutes), DATE_METHOD_ENTRY("setUTCMinutes", builtin_Date_setUTCMinutes), DATE_METHOD_ENTRY("setHours", builtin_Date_setHours), DATE_METHOD_ENTRY("setUTCHours", builtin_Date_setUTCHours), DATE_METHOD_ENTRY("setDate", builtin_Date_setDate), DATE_METHOD_ENTRY("setUTCDate", builtin_Date_setUTCDate), DATE_METHOD_ENTRY("setMonth", builtin_Date_setMonth), DATE_METHOD_ENTRY("setUTCMonth", builtin_Date_setUTCMonth), DATE_METHOD_ENTRY("setYear", builtin_Date_setYear), DATE_METHOD_ENTRY("setFullYear", builtin_Date_setFullYear), DATE_METHOD_ENTRY("setUTCFullYear", builtin_Date_setUTCFullYear), DATE_METHOD_ENTRY("toJSON", builtin_Date_toJSON), }; date_define_methods(js, date_proto, kDateProtoMethods, DATE_COUNT_OF(kDateProtoMethods)); ant_value_t to_primitive_sym = get_toPrimitive_sym(); if (vtype(to_primitive_sym) == T_SYMBOL) { js_set_sym(js, date_proto, to_primitive_sym, js_mkfun(date_toPrimitive)); } ant_value_t date_ctor_obj = js_mkobj(js); js_set_proto_init(date_ctor_obj, function_proto); js_set_slot(date_ctor_obj, SLOT_CFUNC, js_mkfun(builtin_Date)); static const date_method_entry_t kDateCtorMethods[] = { DATE_METHOD_ENTRY("now", builtin_Date_now), DATE_METHOD_ENTRY("parse", builtin_Date_parse), DATE_METHOD_ENTRY("UTC", builtin_Date_UTC), }; date_define_methods(js, date_ctor_obj, kDateCtorMethods, DATE_COUNT_OF(kDateCtorMethods)); js_setprop_nonconfigurable(js, date_ctor_obj, "prototype", 9, date_proto); js_setprop(js, date_ctor_obj, ANT_STRING("name"), ANT_STRING("Date")); ant_value_t date_ctor_func = js_obj_to_func(date_ctor_obj); js_setprop(js, glob, js_mkstr(js, "Date", 4), date_ctor_func); js_setprop(js, date_proto, js_mkstr(js, "constructor", 11), date_ctor_func); js_set_descriptor(js, date_proto, "constructor", 11, JS_DESC_W | JS_DESC_C); }