Skip to content

Commit 65ecb0b

Browse files
committed
Improve Date.parse, small fixes
- add `minimum_length` to enforce array length validation - add `JS_NewDate()` API - add `[Symbol.toStringTag]` property in the global object - simplify `string_get_milliseconds` - support more timezone abbrevs using `string_get_tzabbr` and array
1 parent 6a89d7c commit 65ecb0b

File tree

3 files changed

+81
-58
lines changed

3 files changed

+81
-58
lines changed

cutils.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,12 @@
5151
#define container_of(ptr, type, member) ((type *)((uint8_t *)(ptr) - offsetof(type, member)))
5252
#endif
5353

54+
#if !defined(_MSC_VER) && defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L
55+
#define minimum_length(n) static n
56+
#else
57+
#define minimum_length(n) n
58+
#endif
59+
5460
typedef int BOOL;
5561

5662
#ifndef FALSE

quickjs.c

Lines changed: 73 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -968,6 +968,7 @@ struct JSObject {
968968
} u;
969969
/* byte sizes: 40/48/72 */
970970
};
971+
971972
enum {
972973
__JS_ATOM_NULL = JS_ATOM_NULL,
973974
#define DEF(name, str) JS_ATOM_ ## name,
@@ -1103,6 +1104,12 @@ static void js_operator_set_finalizer(JSRuntime *rt, JSValue val);
11031104
static void js_operator_set_mark(JSRuntime *rt, JSValueConst val,
11041105
JS_MarkFunc *mark_func);
11051106
#endif
1107+
1108+
#define HINT_STRING 0
1109+
#define HINT_NUMBER 1
1110+
#define HINT_NONE 2
1111+
#define HINT_FORCE_ORDINARY (1 << 4) // don't try Symbol.toPrimitive
1112+
static JSValue JS_ToPrimitiveFree(JSContext *ctx, JSValue val, int hint);
11061113
static JSValue JS_ToStringFree(JSContext *ctx, JSValue val);
11071114
static int JS_ToBoolFree(JSContext *ctx, JSValue val);
11081115
static int JS_ToInt32Free(JSContext *ctx, int32_t *pres, JSValue val);
@@ -9848,12 +9855,6 @@ void *JS_GetOpaque2(JSContext *ctx, JSValueConst obj, JSClassID class_id)
98489855
return p;
98499856
}
98509857

9851-
#define HINT_STRING 0
9852-
#define HINT_NUMBER 1
9853-
#define HINT_NONE 2
9854-
/* don't try Symbol.toPrimitive */
9855-
#define HINT_FORCE_ORDINARY (1 << 4)
9856-
98579858
static JSValue JS_ToPrimitiveFree(JSContext *ctx, JSValue val, int hint)
98589859
{
98599860
int i;
@@ -49404,6 +49405,7 @@ static const JSCFunctionListEntry js_global_funcs[] = {
4940449405
JS_PROP_DOUBLE_DEF("Infinity", 1.0 / 0.0, 0 ),
4940549406
JS_PROP_DOUBLE_DEF("NaN", NAN, 0 ),
4940649407
JS_PROP_UNDEFINED_DEF("undefined", 0 ),
49408+
JS_PROP_STRING_DEF("[Symbol.toStringTag]", "global", JS_PROP_CONFIGURABLE ),
4940749409
};
4940849410

4940949411
/* Date */
@@ -49484,7 +49486,7 @@ static char const month_names[] = "JanFebMarAprMayJunJulAugSepOctNovDec";
4948449486
static char const day_names[] = "SunMonTueWedThuFriSat";
4948549487

4948649488
static __exception int get_date_fields(JSContext *ctx, JSValueConst obj,
49487-
double fields[9], int is_local, int force)
49489+
double fields[minimum_length(9)], int is_local, int force)
4948849490
{
4948949491
double dval;
4949049492
int64_t d, days, wd, y, i, md, h, m, s, ms, tz = 0;
@@ -49497,7 +49499,7 @@ static __exception int get_date_fields(JSContext *ctx, JSValueConst obj,
4949749499
return FALSE; /* NaN */
4949849500
d = 0; /* initialize all fields to 0 */
4949949501
} else {
49500-
d = dval;
49502+
d = dval; /* assuming -8.64e15 <= dval <= -8.64e15 */
4950149503
if (is_local) {
4950249504
tz = -getTimezoneOffset(d);
4950349505
d += tz * 60000;
@@ -49545,7 +49547,7 @@ static double time_clip(double t) {
4954549547

4954649548
/* The spec mandates the use of 'double' and it specifies the order
4954749549
of the operations */
49548-
static double set_date_fields(double fields[], int is_local) {
49550+
static double set_date_fields(double fields[minimum_length(7)], int is_local) {
4954949551
double y, m, dt, ym, mn, day, h, s, milli, time, tv;
4955049552
int yi, mi, i;
4955149553
int64_t days;
@@ -49649,9 +49651,9 @@ static JSValue set_date_field(JSContext *ctx, JSValueConst this_val,
4964949651
res = FALSE;
4965049652
fields[first_field + i] = trunc(a);
4965149653
}
49652-
if (res && argc > 0) {
49654+
if (res && argc > 0)
4965349655
d = set_date_fields(fields, is_local);
49654-
}
49656+
4965549657
return JS_SetThisTimeValue(ctx, this_val, d);
4965649658
}
4965749659

@@ -49856,7 +49858,7 @@ static JSValue js_Date_UTC(JSContext *ctx, JSValueConst this_val,
4985649858
int argc, JSValueConst *argv)
4985749859
{
4985849860
// UTC(y, mon, d, h, m, s, ms)
49859-
double fields[9] = { 0, 0, 1, 0, 0, 0, 0, 0, 0 };
49861+
double fields[] = { 0, 0, 1, 0, 0, 0, 0 };
4986049862
int i, n;
4986149863
double a;
4986249864

@@ -49936,14 +49938,15 @@ static BOOL string_get_digits(const uint8_t *sp, int *pp, int *pval,
4993649938
static BOOL string_get_milliseconds(const uint8_t *sp, int *pp, int *pval) {
4993749939
/* parse optional fractional part as milliseconds and truncate. */
4993849940
/* spec does not indicate which rounding should be used */
49939-
int mul = 1000, ms = 0, c, p_start, p = *pp;
49941+
int mul = 100, ms = 0, c, p_start, p = *pp;
4994049942

4994149943
c = sp[p];
4994249944
if (c == '.' || c == ',') {
4994349945
p++;
4994449946
p_start = p;
4994549947
while ((c = sp[p]) >= '0' && c <= '9') {
49946-
ms += (c - '0') * (mul /= 10);
49948+
ms += (c - '0') * mul;
49949+
mul /= 10;
4994749950
p++;
4994849951
if (p - p_start == 9)
4994949952
break;
@@ -49957,7 +49960,11 @@ static BOOL string_get_milliseconds(const uint8_t *sp, int *pp, int *pval) {
4995749960
return TRUE;
4995849961
}
4995949962

49960-
static BOOL string_get_timezone(const uint8_t *sp, int *pp, int *tzp, BOOL strict) {
49963+
static uint8_t upper_ascii(uint8_t c) {
49964+
return c >= 'a' && c <= 'z' ? c - 'a' + 'A' : c;
49965+
}
49966+
49967+
static BOOL string_get_tzoffset(const uint8_t *sp, int *pp, int *tzp, BOOL strict) {
4996149968
int tz = 0, sgn, hh, mm, p = *pp;
4996249969

4996349970
sgn = sp[p++];
@@ -49995,10 +50002,6 @@ static BOOL string_get_timezone(const uint8_t *sp, int *pp, int *tzp, BOOL stric
4999550002
return TRUE;
4999650003
}
4999750004

49998-
static uint8_t upper_ascii(uint8_t c) {
49999-
return c >= 'a' && c <= 'z' ? c - 'a' + 'Z' : c;
50000-
}
50001-
5000250005
static BOOL string_match(const uint8_t *sp, int *pp, const char *s) {
5000350006
int p = *pp;
5000450007
while (*s != '\0') {
@@ -50091,15 +50094,51 @@ static BOOL js_date_parse_isostring(const uint8_t *sp, int fields[9], BOOL *is_l
5009150094
/* parse the time zone offset if present: [+-]HH:mm or [+-]HHmm */
5009250095
if (sp[p]) {
5009350096
*is_local = FALSE;
50094-
if (!string_get_timezone(sp, &p, &fields[8], TRUE))
50097+
if (!string_get_tzoffset(sp, &p, &fields[8], TRUE))
5009550098
return FALSE;
5009650099
}
5009750100
/* error if extraneous characters */
5009850101
return sp[p] == '\0';
5009950102
}
5010050103

50104+
static struct {
50105+
char name[6];
50106+
int16_t offset;
50107+
} const js_tzabbr[] = {
50108+
{ "GMT", 0 }, // Greenwich Mean Time
50109+
{ "UTC", 0 }, // Coordinated Universal Time
50110+
{ "UT", 0 }, // Universal Time
50111+
{ "Z", 0 }, // Zulu Time
50112+
{ "EDT", -4 * 60 }, // Eastern Daylight Time
50113+
{ "EST", -5 * 60 }, // Eastern Standard Time
50114+
{ "CDT", -5 * 60 }, // Central Daylight Time
50115+
{ "CST", -6 * 60 }, // Central Standard Time
50116+
{ "MDT", -6 * 60 }, // Mountain Daylight Time
50117+
{ "MST", -7 * 60 }, // Mountain Standard Time
50118+
{ "PDT", -7 * 60 }, // Pacific Daylight Time
50119+
{ "PST", -8 * 60 }, // Pacific Standard Time
50120+
{ "WET", +0 * 60 }, // Western European Time
50121+
{ "WEST", +1 * 60 }, // Western European Summer Time
50122+
{ "CET", +1 * 60 }, // Central European Time
50123+
{ "CEST", +2 * 60 }, // Central European Summer Time
50124+
{ "EET", +2 * 60 }, // Eastern European Time
50125+
{ "EEST", +3 * 60 }, // Eastern European Summer Time
50126+
};
50127+
50128+
static BOOL string_get_tzabbr(const uint8_t *sp, int *pp, int *offset) {
50129+
for (size_t i = 0; i < countof(js_tzabbr); i++) {
50130+
if (string_match(sp, pp, js_tzabbr[i].name)) {
50131+
*offset = js_tzabbr[i].offset;
50132+
return TRUE;
50133+
}
50134+
}
50135+
return FALSE;
50136+
}
50137+
5010150138
/* parse toString, toUTCString and other formats */
50102-
static BOOL js_date_parse_otherstring(const uint8_t *sp, int fields[9], BOOL *is_local) {
50139+
static BOOL js_date_parse_otherstring(const uint8_t *sp,
50140+
int fields[minimum_length(9)],
50141+
BOOL *is_local) {
5010350142
int c, i, val, p = 0, p_start;
5010450143
int num[3];
5010550144
BOOL has_year = FALSE;
@@ -50119,7 +50158,7 @@ static BOOL js_date_parse_otherstring(const uint8_t *sp, int fields[9], BOOL *is
5011950158
while (string_skip_spaces(sp, &p)) {
5012050159
p_start = p;
5012150160
if ((c = sp[p]) == '+' || c == '-') {
50122-
if (has_time && string_get_timezone(sp, &p, &fields[8], FALSE)) {
50161+
if (has_time && string_get_tzoffset(sp, &p, &fields[8], FALSE)) {
5012350162
*is_local = FALSE;
5012450163
} else {
5012550164
p++;
@@ -50165,11 +50204,6 @@ static BOOL js_date_parse_otherstring(const uint8_t *sp, int fields[9], BOOL *is
5016550204
has_mon = TRUE;
5016650205
string_skip_until(sp, &p, "0123456789 -/(");
5016750206
} else
50168-
if (c == 'Z') {
50169-
*is_local = FALSE;
50170-
p++;
50171-
continue;
50172-
} else
5017350207
if (has_time && string_match(sp, &p, "PM")) {
5017450208
if (fields[3] < 12)
5017550209
fields[3] += 12;
@@ -50180,34 +50214,7 @@ static BOOL js_date_parse_otherstring(const uint8_t *sp, int fields[9], BOOL *is
5018050214
fields[3] -= 12;
5018150215
continue;
5018250216
} else
50183-
if (string_match(sp, &p, "GMT")
50184-
|| string_match(sp, &p, "UTC")
50185-
|| string_match(sp, &p, "UT")) {
50186-
*is_local = FALSE;
50187-
continue;
50188-
} else
50189-
if (string_match(sp, &p, "EDT")) {
50190-
fields[8] = -4 * 60;
50191-
*is_local = FALSE;
50192-
continue;
50193-
} else
50194-
if (string_match(sp, &p, "EST") || string_match(sp, &p, "CDT")) {
50195-
fields[8] = -5 * 60;
50196-
*is_local = FALSE;
50197-
continue;
50198-
} else
50199-
if (string_match(sp, &p, "CST") || string_match(sp, &p, "MDT")) {
50200-
fields[8] = -6 * 60;
50201-
*is_local = FALSE;
50202-
continue;
50203-
} else
50204-
if (string_match(sp, &p, "MST") || string_match(sp, &p, "PDT")) {
50205-
fields[8] = -7 * 60;
50206-
*is_local = FALSE;
50207-
continue;
50208-
} else
50209-
if (string_match(sp, &p, "PST")) {
50210-
fields[8] = -8 * 60;
50217+
if (string_get_tzabbr(sp, &p, &fields[8])) {
5021150218
*is_local = FALSE;
5021250219
continue;
5021350220
} else
@@ -50350,9 +50357,7 @@ static JSValue js_date_Symbol_toPrimitive(JSContext *ctx, JSValueConst this_val,
5035050357
}
5035150358
switch (hint) {
5035250359
case JS_ATOM_number:
50353-
#ifdef CONFIG_BIGNUM
5035450360
case JS_ATOM_integer:
50355-
#endif
5035650361
hint_num = HINT_NUMBER;
5035750362
break;
5035850363
case JS_ATOM_string:
@@ -50376,6 +50381,7 @@ static JSValue js_date_getTimezoneOffset(JSContext *ctx, JSValueConst this_val,
5037650381
if (isnan(v))
5037750382
return JS_NAN;
5037850383
else
50384+
/* assuming -8.64e15 <= v <= -8.64e15 */
5037950385
return JS_NewInt64(ctx, getTimezoneOffset((int64_t)trunc(v)));
5038050386
}
5038150387

@@ -50514,6 +50520,15 @@ static const JSCFunctionListEntry js_date_proto_funcs[] = {
5051450520
JS_CFUNC_DEF("toJSON", 1, js_date_toJSON ),
5051550521
};
5051650522

50523+
JSValue JS_NewDate(JSContext *ctx, double epoch_ms)
50524+
{
50525+
JSValue obj = js_create_from_ctor(ctx, JS_UNDEFINED, JS_CLASS_DATE);
50526+
if (JS_IsException(obj))
50527+
return JS_EXCEPTION;
50528+
JS_SetObjectData(ctx, obj, __JS_NewFloat64(ctx, time_clip(epoch_ms)));
50529+
return obj;
50530+
}
50531+
5051750532
void JS_AddIntrinsicDate(JSContext *ctx)
5051850533
{
5051950534
JSValueConst obj;
@@ -52733,7 +52748,7 @@ void JS_AddIntrinsicBaseObjects(JSContext *ctx)
5273352748
/* XXX: create auto_initializer */
5273452749
{
5273552750
/* initialize Array.prototype[Symbol.unscopables] */
52736-
char const unscopables[] =
52751+
static const char unscopables[] =
5273752752
"copyWithin" "\0"
5273852753
"entries" "\0"
5273952754
"fill" "\0"

quickjs.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -724,6 +724,8 @@ JS_BOOL JS_SetConstructorBit(JSContext *ctx, JSValueConst func_obj, JS_BOOL val)
724724
JSValue JS_NewArray(JSContext *ctx);
725725
int JS_IsArray(JSContext *ctx, JSValueConst val);
726726

727+
JSValue JS_NewDate(JSContext *ctx, double epoch_ms);
728+
727729
JSValue JS_GetPropertyInternal(JSContext *ctx, JSValueConst obj,
728730
JSAtom prop, JSValueConst receiver,
729731
JS_BOOL throw_ref_error);

0 commit comments

Comments
 (0)