#include <compat.h> // IWYU pragma: keep

#include <math.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <sys/timeb.h>
#else
#include <sys/time.h>
#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);
}
