cosmopolitan/third_party/quickjs/date.c

1035 lines
32 KiB
C
Raw Normal View History

/*
* QuickJS Javascript Engine
*
* Copyright (c) 2017-2021 Fabrice Bellard
* Copyright (c) 2017-2021 Charlie Gordon
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "libc/calls/struct/timeval.h"
#include "libc/calls/weirdtypes.h"
#include "libc/fmt/fmt.h"
#include "libc/time/struct/tm.h"
#include "libc/time/time.h"
#include "third_party/quickjs/internal.h"
#include "third_party/quickjs/libregexp.h"
#include "third_party/quickjs/quickjs.h"
asm(".ident\t\"\\n\\n\
QuickJS (MIT License)\\n\
Copyright (c) 2017-2021 Fabrice Bellard\\n\
Copyright (c) 2017-2021 Charlie Gordon\"");
asm(".include \"libc/disclaimer.inc\"");
/* clang-format off */
#if 0
/* OS dependent: return the UTC time in ms since 1970. */
static JSValue js___date_now(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv)
{
int64_t d;
struct timeval tv;
gettimeofday(&tv, NULL);
d = (int64_t)tv.tv_sec * 1000 + (tv.tv_usec / 1000);
return JS_NewInt64(ctx, d);
}
#endif
/* OS dependent: return the UTC time in microseconds since 1970. */
JSValue js___date_clock(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
int64_t d;
struct timeval tv;
gettimeofday(&tv, NULL);
d = (int64_t)tv.tv_sec * 1000000 + tv.tv_usec;
return JS_NewInt64(ctx, d);
}
/* OS dependent. d = argv[0] is in ms from 1970. Return the difference
between UTC time and local time 'd' in minutes */
static int getTimezoneOffset(int64_t time) {
#if defined(_WIN32)
/* XXX: TODO */
return 0;
#else
time_t ti;
struct tm tm;
time /= 1000; /* convert to seconds */
if (sizeof(time_t) == 4) {
/* on 32-bit systems, we need to clamp the time value to the
range of `time_t`. This is better than truncating values to
32 bits and hopefully provides the same result as 64-bit
implementation of localtime_r.
*/
if ((time_t)-1 < 0) {
if (time < INT32_MIN) {
time = INT32_MIN;
} else if (time > INT32_MAX) {
time = INT32_MAX;
}
} else {
if (time < 0) {
time = 0;
} else if (time > UINT32_MAX) {
time = UINT32_MAX;
}
}
}
ti = time;
localtime_r(&ti, &tm);
return -tm.tm_gmtoff / 60;
#endif
}
#if 0
static JSValue js___date_getTimezoneOffset(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv)
{
double dd;
if (JS_ToFloat64(ctx, &dd, argv[0]))
return JS_EXCEPTION;
if (isnan(dd))
return __JS_NewFloat64(ctx, dd);
else
return JS_NewInt32(ctx, getTimezoneOffset((int64_t)dd));
}
static JSValue js_get_prototype_from_ctor(JSContext *ctx, JSValueConst ctor,
JSValueConst def_proto)
{
JSValue proto;
proto = JS_GetProperty(ctx, ctor, JS_ATOM_prototype);
if (JS_IsException(proto))
return proto;
if (!JS_IsObject(proto)) {
JS_FreeValue(ctx, proto);
proto = JS_DupValue(ctx, def_proto);
}
return proto;
}
/* create a new date object */
static JSValue js___date_create(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv)
{
JSValue obj, proto;
proto = js_get_prototype_from_ctor(ctx, argv[0], argv[1]);
if (JS_IsException(proto))
return proto;
obj = JS_NewObjectProtoClass(ctx, proto, JS_CLASS_DATE);
JS_FreeValue(ctx, proto);
if (!JS_IsException(obj))
JS_SetObjectData(ctx, obj, JS_DupValue(ctx, argv[2]));
return obj;
}
#endif
static int64_t math_mod(int64_t a, int64_t b) {
/* return positive modulo */
int64_t m = a % b;
return m + (m < 0) * b;
}
static int64_t floor_div(int64_t a, int64_t b) {
/* integer division rounding toward -Infinity */
int64_t m = a % b;
return (a - (m + (m < 0) * b)) / b;
}
static JSValue js_Date_parse(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv);
static __exception int JS_ThisTimeValue(JSContext *ctx, double *valp, JSValueConst this_val)
{
if (JS_VALUE_GET_TAG(this_val) == JS_TAG_OBJECT) {
JSObject *p = JS_VALUE_GET_OBJ(this_val);
if (p->class_id == JS_CLASS_DATE && JS_IsNumber(p->u.object_data))
return JS_ToFloat64(ctx, valp, p->u.object_data);
}
JS_ThrowTypeError(ctx, "not a Date object");
return -1;
}
static JSValue JS_SetThisTimeValue(JSContext *ctx, JSValueConst this_val, double v)
{
if (JS_VALUE_GET_TAG(this_val) == JS_TAG_OBJECT) {
JSObject *p = JS_VALUE_GET_OBJ(this_val);
if (p->class_id == JS_CLASS_DATE) {
JS_FreeValue(ctx, p->u.object_data);
p->u.object_data = JS_NewFloat64(ctx, v);
return JS_DupValue(ctx, p->u.object_data);
}
}
return JS_ThrowTypeError(ctx, "not a Date object");
}
static int64_t days_from_year(int64_t y) {
return 365 * (y - 1970) + floor_div(y - 1969, 4) -
floor_div(y - 1901, 100) + floor_div(y - 1601, 400);
}
static int64_t days_in_year(int64_t y) {
return 365 + !(y % 4) - !(y % 100) + !(y % 400);
}
/* return the year, update days */
static int64_t year_from_days(int64_t *days) {
int64_t y, d1, nd, d = *days;
y = floor_div(d * 10000, 3652425) + 1970;
/* the initial approximation is very good, so only a few
iterations are necessary */
for(;;) {
d1 = d - days_from_year(y);
if (d1 < 0) {
y--;
d1 += days_in_year(y);
} else {
nd = days_in_year(y);
if (d1 < nd)
break;
d1 -= nd;
y++;
}
}
*days = d1;
return y;
}
static int const month_days[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
static char const month_names[] = "JanFebMarAprMayJunJulAugSepOctNovDec";
static char const day_names[] = "SunMonTueWedThuFriSat";
static __exception int get_date_fields(JSContext *ctx, JSValueConst obj,
double fields[9], int is_local, int force)
{
double dval;
int64_t d, days, wd, y, i, md, h, m, s, ms, tz = 0;
if (JS_ThisTimeValue(ctx, &dval, obj))
return -1;
if (isnan(dval)) {
if (!force)
return FALSE; /* NaN */
d = 0; /* initialize all fields to 0 */
} else {
d = dval;
if (is_local) {
tz = -getTimezoneOffset(d);
d += tz * 60000;
}
}
/* result is >= 0, we can use % */
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); /* week day */
y = year_from_days(&days);
for(i = 0; i < 11; i++) {
md = month_days[i];
if (i == 1)
md += days_in_year(y) - 365;
if (days < md)
break;
days -= md;
}
fields[0] = y;
fields[1] = i;
fields[2] = days + 1;
fields[3] = h;
fields[4] = m;
fields[5] = s;
fields[6] = ms;
fields[7] = wd;
fields[8] = tz;
return TRUE;
}
static double time_clip(double t) {
if (t >= -8.64e15 && t <= 8.64e15)
return trunc(t) + 0.0; /* convert -0 to +0 */
else
return NAN;
}
/* The spec mandates the use of 'double' and it fixes the order
of the operations */
static double set_date_fields(double fields[], int is_local) {
int64_t y;
double days, d, h, m1;
int i, m, md;
m1 = fields[1];
m = fmod(m1, 12);
if (m < 0)
m += 12;
y = (int64_t)(fields[0] + floor(m1 / 12));
days = days_from_year(y);
for(i = 0; i < m; i++) {
md = month_days[i];
if (i == 1)
md += days_in_year(y) - 365;
days += md;
}
days += fields[2] - 1;
h = fields[3] * 3600000 + fields[4] * 60000 +
fields[5] * 1000 + fields[6];
d = days * 86400000 + h;
if (is_local)
d += getTimezoneOffset(d) * 60000;
return time_clip(d);
}
static JSValue get_date_field(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv, int magic)
{
// get_date_field(obj, n, is_local)
double fields[9];
int res, n, is_local;
is_local = magic & 0x0F;
n = (magic >> 4) & 0x0F;
res = get_date_fields(ctx, this_val, fields, is_local, 0);
if (res < 0)
return JS_EXCEPTION;
if (!res)
return JS_NAN;
if (magic & 0x100) { // getYear
fields[0] -= 1900;
}
return JS_NewFloat64(ctx, fields[n]);
}
static JSValue set_date_field(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv, int magic)
{
// _field(obj, first_field, end_field, args, is_local)
double fields[9];
int res, first_field, end_field, is_local, i, n;
double d, a;
d = NAN;
first_field = (magic >> 8) & 0x0F;
end_field = (magic >> 4) & 0x0F;
is_local = magic & 0x0F;
res = get_date_fields(ctx, this_val, fields, is_local, first_field == 0);
if (res < 0)
return JS_EXCEPTION;
if (res && argc > 0) {
n = end_field - first_field;
if (argc < n)
n = argc;
for(i = 0; i < n; i++) {
if (JS_ToFloat64(ctx, &a, argv[i]))
return JS_EXCEPTION;
if (!isfinite(a))
goto done;
fields[first_field + i] = trunc(a);
}
d = set_date_fields(fields, is_local);
}
done:
return JS_SetThisTimeValue(ctx, this_val, d);
}
/* fmt:
0: toUTCString: "Tue, 02 Jan 2018 23:04:46 GMT"
1: toString: "Wed Jan 03 2018 00:05:22 GMT+0100 (CET)"
2: toISOString: "2018-01-02T23:02:56.927Z"
3: toLocaleString: "1/2/2018, 11:40:40 PM"
part: 1=date, 2=time 3=all
XXX: should use a variant of strftime().
*/
static JSValue get_date_string(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv, int magic)
{
// _string(obj, fmt, part)
char buf[64];
double fields[9];
int res, fmt, part, pos;
int y, mon, d, h, m, s, ms, wd, tz;
fmt = (magic >> 4) & 0x0F;
part = magic & 0x0F;
res = get_date_fields(ctx, this_val, fields, fmt & 1, 0);
if (res < 0)
return JS_EXCEPTION;
if (!res) {
if (fmt == 2)
return JS_ThrowRangeError(ctx, "Date value is NaN");
else
return JS_NewString(ctx, "Invalid Date");
}
y = fields[0];
mon = fields[1];
d = fields[2];
h = fields[3];
m = fields[4];
s = fields[5];
ms = fields[6];
wd = fields[7];
tz = fields[8];
pos = 0;
if (part & 1) { /* date part */
switch(fmt) {
case 0:
pos += snprintf(buf + pos, sizeof(buf) - 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) - pos,
"%.3s %.3s %02d %0*d",
day_names + wd * 3,
month_names + mon * 3, d, 4 + (y < 0), y);
if (part == 3) {
buf[pos++] = ' ';
}
break;
case 2:
if (y >= 0 && y <= 9999) {
pos += snprintf(buf + pos, sizeof(buf) - pos,
"%04d", y);
} else {
pos += snprintf(buf + pos, sizeof(buf) - pos,
"%+07d", y);
}
pos += snprintf(buf + pos, sizeof(buf) - pos,
"-%02d-%02dT", mon + 1, d);
break;
case 3:
pos += snprintf(buf + pos, sizeof(buf) - pos,
"%02d/%02d/%0*d", mon + 1, d, 4 + (y < 0), y);
if (part == 3) {
buf[pos++] = ',';
buf[pos++] = ' ';
}
break;
}
}
if (part & 2) { /* time part */
switch(fmt) {
case 0:
pos += snprintf(buf + pos, sizeof(buf) - pos,
"%02d:%02d:%02d GMT", h, m, s);
break;
case 1:
pos += snprintf(buf + pos, sizeof(buf) - pos,
"%02d:%02d:%02d GMT", h, m, s);
if (tz < 0) {
buf[pos++] = '-';
tz = -tz;
} else {
buf[pos++] = '+';
}
/* tz is >= 0, can use % */
pos += snprintf(buf + pos, sizeof(buf) - pos,
"%02d%02d", tz / 60, tz % 60);
/* XXX: tack the time zone code? */
break;
case 2:
pos += snprintf(buf + pos, sizeof(buf) - pos,
"%02d:%02d:%02d.%03dZ", h, m, s, ms);
break;
case 3:
pos += snprintf(buf + pos, sizeof(buf) - pos,
"%02d:%02d:%02d %cM", (h + 1) % 12 - 1, m, s,
(h < 12) ? 'A' : 'P');
break;
}
}
return JS_NewStringLen(ctx, buf, pos);
}
/* OS dependent: return the UTC time in ms since 1970. */
static int64_t date_now(void) {
struct timeval tv;
gettimeofday(&tv, NULL);
return (int64_t)tv.tv_sec * 1000 + (tv.tv_usec / 1000);
}
static JSValue js_date_constructor(JSContext *ctx, JSValueConst new_target,
int argc, JSValueConst *argv)
{
// Date(y, mon, d, h, m, s, ms)
JSValue rv;
int i, n;
double a, val;
if (JS_IsUndefined(new_target)) {
/* invoked as function */
argc = 0;
}
n = argc;
if (n == 0) {
val = date_now();
} else if (n == 1) {
JSValue v, dv;
if (JS_VALUE_GET_TAG(argv[0]) == JS_TAG_OBJECT) {
JSObject *p = JS_VALUE_GET_OBJ(argv[0]);
if (p->class_id == JS_CLASS_DATE && JS_IsNumber(p->u.object_data)) {
if (JS_ToFloat64(ctx, &val, p->u.object_data))
return JS_EXCEPTION;
val = time_clip(val);
goto has_val;
}
}
v = JS_ToPrimitive(ctx, argv[0], HINT_NONE);
if (JS_IsString(v)) {
dv = js_Date_parse(ctx, JS_UNDEFINED, 1, (JSValueConst *)&v);
JS_FreeValue(ctx, v);
if (JS_IsException(dv))
return JS_EXCEPTION;
if (JS_ToFloat64Free(ctx, &val, dv))
return JS_EXCEPTION;
} else {
if (JS_ToFloat64Free(ctx, &val, v))
return JS_EXCEPTION;
}
val = time_clip(val);
} else {
double fields[] = { 0, 0, 1, 0, 0, 0, 0 };
if (n > 7)
n = 7;
for(i = 0; i < n; i++) {
if (JS_ToFloat64(ctx, &a, argv[i]))
return JS_EXCEPTION;
if (!isfinite(a))
break;
fields[i] = trunc(a);
if (i == 0 && fields[0] >= 0 && fields[0] < 100)
fields[0] += 1900;
}
val = (i == n) ? set_date_fields(fields, 1) : NAN;
}
has_val:
#if 0
JSValueConst args[3];
args[0] = new_target;
args[1] = ctx->class_proto[JS_CLASS_DATE];
args[2] = JS_NewFloat64(ctx, val);
rv = js___date_create(ctx, JS_UNDEFINED, 3, args);
#else
rv = js_create_from_ctor(ctx, new_target, JS_CLASS_DATE);
if (!JS_IsException(rv))
JS_SetObjectData(ctx, rv, JS_NewFloat64(ctx, val));
#endif
if (!JS_IsException(rv) && JS_IsUndefined(new_target)) {
/* invoked as a function, return (new Date()).toString(); */
JSValue s;
s = get_date_string(ctx, rv, 0, NULL, 0x13);
JS_FreeValue(ctx, rv);
rv = s;
}
return rv;
}
static JSValue js_Date_UTC(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv)
{
// UTC(y, mon, d, h, m, s, ms)
double fields[] = { 0, 0, 1, 0, 0, 0, 0 };
int i, n;
double a;
n = argc;
if (n == 0)
return JS_NAN;
if (n > 7)
n = 7;
for(i = 0; i < n; i++) {
if (JS_ToFloat64(ctx, &a, argv[i]))
return JS_EXCEPTION;
if (!isfinite(a))
return JS_NAN;
fields[i] = trunc(a);
if (i == 0 && fields[0] >= 0 && fields[0] < 100)
fields[0] += 1900;
}
return JS_NewFloat64(ctx, set_date_fields(fields, 0));
}
static void string_skip_spaces(JSString *sp, int *pp) {
while (*pp < sp->len && string_get(sp, *pp) == ' ')
*pp += 1;
}
static void string_skip_non_spaces(JSString *sp, int *pp) {
while (*pp < sp->len && string_get(sp, *pp) != ' ')
*pp += 1;
}
/* parse a numeric field with an optional sign if accept_sign is TRUE */
static int string_get_digits(JSString *sp, int *pp, int64_t *pval) {
int64_t v = 0;
int c, p = *pp, p_start;
if (p >= sp->len)
return -1;
p_start = p;
while (p < sp->len) {
c = string_get(sp, p);
if (!(c >= '0' && c <= '9')) {
if (p == p_start)
return -1;
else
break;
}
v = v * 10 + c - '0';
p++;
}
*pval = v;
*pp = p;
return 0;
}
static int string_get_signed_digits(JSString *sp, int *pp, int64_t *pval) {
int res, sgn, p = *pp;
if (p >= sp->len)
return -1;
sgn = string_get(sp, p);
if (sgn == '-' || sgn == '+')
p++;
res = string_get_digits(sp, &p, pval);
if (res == 0 && sgn == '-')
*pval = -*pval;
*pp = p;
return res;
}
/* parse a fixed width numeric field */
static int string_get_fixed_width_digits(JSString *sp, int *pp, int n, int64_t *pval) {
int64_t v = 0;
int i, c, p = *pp;
for(i = 0; i < n; i++) {
if (p >= sp->len)
return -1;
c = string_get(sp, p);
if (!(c >= '0' && c <= '9'))
return -1;
v = v * 10 + c - '0';
p++;
}
*pval = v;
*pp = p;
return 0;
}
static int string_get_milliseconds(JSString *sp, int *pp, int64_t *pval) {
/* parse milliseconds as a fractional part, round to nearest */
/* XXX: the spec does not indicate which rounding should be used */
int mul = 1000, ms = 0, p = *pp, c, p_start;
if (p >= sp->len)
return -1;
p_start = p;
while (p < sp->len) {
c = string_get(sp, p);
if (!(c >= '0' && c <= '9')) {
if (p == p_start)
return -1;
else
break;
}
if (mul == 1 && c >= '5')
ms += 1;
ms += (c - '0') * (mul /= 10);
p++;
}
*pval = ms;
*pp = p;
return 0;
}
static int find_abbrev(JSString *sp, int p, const char *list, int count) {
int n, i;
if (p + 3 <= sp->len) {
for (n = 0; n < count; n++) {
for (i = 0; i < 3; i++) {
if (string_get(sp, p + i) != month_names[n * 3 + i])
goto next;
}
return n;
next:;
}
}
return -1;
}
static int string_get_month(JSString *sp, int *pp, int64_t *pval) {
int n;
string_skip_spaces(sp, pp);
n = find_abbrev(sp, *pp, month_names, 12);
if (n < 0)
return -1;
*pval = n;
*pp += 3;
return 0;
}
static JSValue js_Date_parse(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv)
{
// parse(s)
JSValue s, rv;
int64_t fields[] = { 0, 1, 1, 0, 0, 0, 0 };
double fields1[7];
int64_t tz, hh, mm;
double d;
int p, i, c, sgn, l;
JSString *sp;
BOOL is_local;
rv = JS_NAN;
s = JS_ToString(ctx, argv[0]);
if (JS_IsException(s))
return JS_EXCEPTION;
sp = JS_VALUE_GET_STRING(s);
p = 0;
if (p < sp->len && (((c = string_get(sp, p)) >= '0' && c <= '9') || c == '+' || c == '-')) {
/* ISO format */
/* year field can be negative */
if (string_get_signed_digits(sp, &p, &fields[0]))
goto done;
for (i = 1; i < 7; i++) {
if (p >= sp->len)
break;
switch(i) {
case 1:
case 2:
c = '-';
break;
case 3:
c = 'T';
break;
case 4:
case 5:
c = ':';
break;
case 6:
c = '.';
break;
}
if (string_get(sp, p) != c)
break;
p++;
if (i == 6) {
if (string_get_milliseconds(sp, &p, &fields[i]))
goto done;
} else {
if (string_get_digits(sp, &p, &fields[i]))
goto done;
}
}
/* no time: UTC by default */
is_local = (i > 3);
fields[1] -= 1;
/* parse the time zone offset if present: [+-]HH:mm or [+-]HHmm */
tz = 0;
if (p < sp->len) {
sgn = string_get(sp, p);
if (sgn == '+' || sgn == '-') {
p++;
l = sp->len - p;
if (l != 4 && l != 5)
goto done;
if (string_get_fixed_width_digits(sp, &p, 2, &hh))
goto done;
if (l == 5) {
if (string_get(sp, p) != ':')
goto done;
p++;
}
if (string_get_fixed_width_digits(sp, &p, 2, &mm))
goto done;
tz = hh * 60 + mm;
if (sgn == '-')
tz = -tz;
is_local = FALSE;
} else if (sgn == 'Z') {
p++;
is_local = FALSE;
} else {
goto done;
}
/* error if extraneous characters */
if (p != sp->len)
goto done;
}
} else {
/* toString or toUTCString format */
/* skip the day of the week */
string_skip_non_spaces(sp, &p);
string_skip_spaces(sp, &p);
if (p >= sp->len)
goto done;
c = string_get(sp, p);
if (c >= '0' && c <= '9') {
/* day of month first */
if (string_get_digits(sp, &p, &fields[2]))
goto done;
if (string_get_month(sp, &p, &fields[1]))
goto done;
} else {
/* month first */
if (string_get_month(sp, &p, &fields[1]))
goto done;
string_skip_spaces(sp, &p);
if (string_get_digits(sp, &p, &fields[2]))
goto done;
}
/* year */
string_skip_spaces(sp, &p);
if (string_get_signed_digits(sp, &p, &fields[0]))
goto done;
/* hour, min, seconds */
string_skip_spaces(sp, &p);
for(i = 0; i < 3; i++) {
if (i == 1 || i == 2) {
if (p >= sp->len)
goto done;
if (string_get(sp, p) != ':')
goto done;
p++;
}
if (string_get_digits(sp, &p, &fields[3 + i]))
goto done;
}
// XXX: parse optional milliseconds?
/* parse the time zone offset if present: [+-]HHmm */
is_local = FALSE;
tz = 0;
for (tz = 0; p < sp->len; p++) {
sgn = string_get(sp, p);
if (sgn == '+' || sgn == '-') {
p++;
if (string_get_fixed_width_digits(sp, &p, 2, &hh))
goto done;
if (string_get_fixed_width_digits(sp, &p, 2, &mm))
goto done;
tz = hh * 60 + mm;
if (sgn == '-')
tz = -tz;
break;
}
}
}
for(i = 0; i < 7; i++)
fields1[i] = fields[i];
d = set_date_fields(fields1, is_local) - tz * 60000;
rv = JS_NewFloat64(ctx, d);
done:
JS_FreeValue(ctx, s);
return rv;
}
static JSValue js_Date_now(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv)
{
// now()
return JS_NewInt64(ctx, date_now());
}
static JSValue js_date_Symbol_toPrimitive(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv)
{
// Symbol_toPrimitive(hint)
JSValueConst obj = this_val;
JSAtom hint = JS_ATOM_NULL;
int hint_num;
if (!JS_IsObject(obj))
return JS_ThrowTypeErrorNotAnObject(ctx);
if (JS_IsString(argv[0])) {
hint = JS_ValueToAtom(ctx, argv[0]);
if (hint == JS_ATOM_NULL)
return JS_EXCEPTION;
JS_FreeAtom(ctx, hint);
}
switch (hint) {
case JS_ATOM_number:
#ifdef CONFIG_BIGNUM
case JS_ATOM_integer:
#endif
hint_num = HINT_NUMBER;
break;
case JS_ATOM_string:
case JS_ATOM_default:
hint_num = HINT_STRING;
break;
default:
return JS_ThrowTypeError(ctx, "invalid hint");
}
return JS_ToPrimitive(ctx, obj, hint_num | HINT_FORCE_ORDINARY);
}
static JSValue js_date_getTimezoneOffset(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv)
{
// getTimezoneOffset()
double v;
if (JS_ThisTimeValue(ctx, &v, this_val))
return JS_EXCEPTION;
if (isnan(v))
return JS_NAN;
else
return JS_NewInt64(ctx, getTimezoneOffset((int64_t)trunc(v)));
}
static JSValue js_date_getTime(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv)
{
// getTime()
double v;
if (JS_ThisTimeValue(ctx, &v, this_val))
return JS_EXCEPTION;
return JS_NewFloat64(ctx, v);
}
static JSValue js_date_setTime(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv)
{
// setTime(v)
double v;
if (JS_ThisTimeValue(ctx, &v, this_val) || JS_ToFloat64(ctx, &v, argv[0]))
return JS_EXCEPTION;
return JS_SetThisTimeValue(ctx, this_val, time_clip(v));
}
static JSValue js_date_setYear(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv)
{
// setYear(y)
double y;
JSValueConst args[1];
if (JS_ThisTimeValue(ctx, &y, this_val) || JS_ToFloat64(ctx, &y, argv[0]))
return JS_EXCEPTION;
y = +y;
if (isfinite(y)) {
y = trunc(y);
if (y >= 0 && y < 100)
y += 1900;
}
args[0] = JS_NewFloat64(ctx, y);
return set_date_field(ctx, this_val, 1, args, 0x011);
}
static JSValue js_date_toJSON(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv)
{
// toJSON(key)
JSValue obj, tv, method, rv;
double d;
rv = JS_EXCEPTION;
tv = JS_UNDEFINED;
obj = JS_ToObject(ctx, this_val);
tv = JS_ToPrimitive(ctx, obj, HINT_NUMBER);
if (JS_IsException(tv))
goto exception;
if (JS_IsNumber(tv)) {
if (JS_ToFloat64(ctx, &d, tv) < 0)
goto exception;
if (!isfinite(d)) {
rv = JS_NULL;
goto done;
}
}
method = JS_GetPropertyStr(ctx, obj, "toISOString");
if (JS_IsException(method))
goto exception;
if (!JS_IsFunction(ctx, method)) {
JS_ThrowTypeError(ctx, "object needs toISOString method");
JS_FreeValue(ctx, method);
goto exception;
}
rv = JS_CallFree(ctx, method, obj, 0, NULL);
exception:
done:
JS_FreeValue(ctx, obj);
JS_FreeValue(ctx, tv);
return rv;
}
static const JSCFunctionListEntry js_date_funcs[] = {
JS_CFUNC_DEF("now", 0, js_Date_now ),
JS_CFUNC_DEF("parse", 1, js_Date_parse ),
JS_CFUNC_DEF("UTC", 7, js_Date_UTC ),
};
static const JSCFunctionListEntry js_date_proto_funcs[] = {
JS_CFUNC_DEF("valueOf", 0, js_date_getTime ),
JS_CFUNC_MAGIC_DEF("toString", 0, get_date_string, 0x13 ),
JS_CFUNC_DEF("[Symbol.toPrimitive]", 1, js_date_Symbol_toPrimitive ),
JS_CFUNC_MAGIC_DEF("toUTCString", 0, get_date_string, 0x03 ),
JS_ALIAS_DEF("toGMTString", "toUTCString" ),
JS_CFUNC_MAGIC_DEF("toISOString", 0, get_date_string, 0x23 ),
JS_CFUNC_MAGIC_DEF("toDateString", 0, get_date_string, 0x11 ),
JS_CFUNC_MAGIC_DEF("toTimeString", 0, get_date_string, 0x12 ),
JS_CFUNC_MAGIC_DEF("toLocaleString", 0, get_date_string, 0x33 ),
JS_CFUNC_MAGIC_DEF("toLocaleDateString", 0, get_date_string, 0x31 ),
JS_CFUNC_MAGIC_DEF("toLocaleTimeString", 0, get_date_string, 0x32 ),
JS_CFUNC_DEF("getTimezoneOffset", 0, js_date_getTimezoneOffset ),
JS_CFUNC_DEF("getTime", 0, js_date_getTime ),
JS_CFUNC_MAGIC_DEF("getYear", 0, get_date_field, 0x101 ),
JS_CFUNC_MAGIC_DEF("getFullYear", 0, get_date_field, 0x01 ),
JS_CFUNC_MAGIC_DEF("getUTCFullYear", 0, get_date_field, 0x00 ),
JS_CFUNC_MAGIC_DEF("getMonth", 0, get_date_field, 0x11 ),
JS_CFUNC_MAGIC_DEF("getUTCMonth", 0, get_date_field, 0x10 ),
JS_CFUNC_MAGIC_DEF("getDate", 0, get_date_field, 0x21 ),
JS_CFUNC_MAGIC_DEF("getUTCDate", 0, get_date_field, 0x20 ),
JS_CFUNC_MAGIC_DEF("getHours", 0, get_date_field, 0x31 ),
JS_CFUNC_MAGIC_DEF("getUTCHours", 0, get_date_field, 0x30 ),
JS_CFUNC_MAGIC_DEF("getMinutes", 0, get_date_field, 0x41 ),
JS_CFUNC_MAGIC_DEF("getUTCMinutes", 0, get_date_field, 0x40 ),
JS_CFUNC_MAGIC_DEF("getSeconds", 0, get_date_field, 0x51 ),
JS_CFUNC_MAGIC_DEF("getUTCSeconds", 0, get_date_field, 0x50 ),
JS_CFUNC_MAGIC_DEF("getMilliseconds", 0, get_date_field, 0x61 ),
JS_CFUNC_MAGIC_DEF("getUTCMilliseconds", 0, get_date_field, 0x60 ),
JS_CFUNC_MAGIC_DEF("getDay", 0, get_date_field, 0x71 ),
JS_CFUNC_MAGIC_DEF("getUTCDay", 0, get_date_field, 0x70 ),
JS_CFUNC_DEF("setTime", 1, js_date_setTime ),
JS_CFUNC_MAGIC_DEF("setMilliseconds", 1, set_date_field, 0x671 ),
JS_CFUNC_MAGIC_DEF("setUTCMilliseconds", 1, set_date_field, 0x670 ),
JS_CFUNC_MAGIC_DEF("setSeconds", 2, set_date_field, 0x571 ),
JS_CFUNC_MAGIC_DEF("setUTCSeconds", 2, set_date_field, 0x570 ),
JS_CFUNC_MAGIC_DEF("setMinutes", 3, set_date_field, 0x471 ),
JS_CFUNC_MAGIC_DEF("setUTCMinutes", 3, set_date_field, 0x470 ),
JS_CFUNC_MAGIC_DEF("setHours", 4, set_date_field, 0x371 ),
JS_CFUNC_MAGIC_DEF("setUTCHours", 4, set_date_field, 0x370 ),
JS_CFUNC_MAGIC_DEF("setDate", 1, set_date_field, 0x231 ),
JS_CFUNC_MAGIC_DEF("setUTCDate", 1, set_date_field, 0x230 ),
JS_CFUNC_MAGIC_DEF("setMonth", 2, set_date_field, 0x131 ),
JS_CFUNC_MAGIC_DEF("setUTCMonth", 2, set_date_field, 0x130 ),
JS_CFUNC_DEF("setYear", 1, js_date_setYear ),
JS_CFUNC_MAGIC_DEF("setFullYear", 3, set_date_field, 0x031 ),
JS_CFUNC_MAGIC_DEF("setUTCFullYear", 3, set_date_field, 0x030 ),
JS_CFUNC_DEF("toJSON", 1, js_date_toJSON ),
};
void JS_AddIntrinsicDate(JSContext *ctx)
{
JSValueConst obj;
/* Date */
ctx->class_proto[JS_CLASS_DATE] = JS_NewObject(ctx);
JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_DATE], js_date_proto_funcs,
countof(js_date_proto_funcs));
obj = JS_NewGlobalCConstructor(ctx, "Date", js_date_constructor, 7,
ctx->class_proto[JS_CLASS_DATE]);
JS_SetPropertyFunctionList(ctx, obj, js_date_funcs, countof(js_date_funcs));
}