From b0a38d5b1f999724a735316af7a42cb28dfa9dfc Mon Sep 17 00:00:00 2001 From: Dmitry Volyntsev Date: Fri, 1 Nov 2019 19:26:42 +0300 Subject: [PATCH] Fixed "Date" object string formatting. 1) Getting rid of strftime() as it is locale dependent whereas output should always be in english. 2) Unifying all formatters in a single function. --- src/njs_date.c | 291 +++++++++++++++++++++------------------ src/test/njs_unit_test.c | 34 ++++- 2 files changed, 193 insertions(+), 132 deletions(-) diff --git a/src/njs_date.c b/src/njs_date.c index 43e9d8c7..ca681e80 100644 --- a/src/njs_date.c +++ b/src/njs_date.c @@ -14,14 +14,19 @@ * FreeBSD and MacOSX timegm() cannot handle years before 1900. */ -#define NJS_ISO_DATE_TIME_LEN sizeof("+001970-09-28T12:00:00.000Z") - -#define NJS_HTTP_DATE_TIME_LEN sizeof("Mon, 28 Sep 1970 12:00:00 GMT") - #define NJS_DATE_TIME_LEN \ sizeof("Mon Sep 28 1970 12:00:00 GMT+0600 (XXXXX)") +typedef enum { + NJS_DATE_FMT_TO_TIME_STRING, + NJS_DATE_FMT_TO_DATE_STRING, + NJS_DATE_FMT_TO_STRING, + NJS_DATE_FMT_TO_UTC_STRING, + NJS_DATE_FMT_TO_ISO_STRING, +} njs_date_fmt_t; + + static double njs_date_string_parse(njs_value_t *date); static double njs_date_rfc2822_string_parse(struct tm *tm, const u_char *p, const u_char *end); @@ -36,7 +41,8 @@ static njs_int_t njs_date_gmtoff_parse(const u_char *start, const u_char *end); static const u_char *njs_date_number_parse(int *value, const u_char *p, const u_char *end, size_t size); static int64_t njs_timegm(struct tm *tm); -static njs_int_t njs_date_string(njs_vm_t *vm, const char *fmt, double time); +static njs_int_t njs_date_string(njs_vm_t *vm, njs_value_t *retval, + njs_date_fmt_t fmt, double time); static double njs_date_time(struct tm *tm, int64_t ms); static double njs_date_utc_time(struct tm *tm, double time); @@ -179,94 +185,94 @@ njs_date_constructor(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_date_t *date; int64_t values[8]; - if (vm->top_frame->ctor) { + if (!vm->top_frame->ctor) { + return njs_date_string(vm, &vm->retval, NJS_DATE_FMT_TO_STRING, + njs_gettime()); + } - if (nargs == 1) { - time = njs_gettime(); + if (nargs == 1) { + time = njs_gettime(); - } else if (nargs == 2) { - if (njs_is_object(&args[1])) { - if (!njs_is_date(&args[1])) { - ret = njs_value_to_primitive(vm, &args[1], &args[1], 0); - if (ret != NJS_OK) { - return ret; - } + } else if (nargs == 2) { + if (njs_is_object(&args[1])) { + if (!njs_is_date(&args[1])) { + ret = njs_value_to_primitive(vm, &args[1], &args[1], 0); + if (njs_slow_path(ret != NJS_OK)) { + return ret; } } + } - if (njs_is_date(&args[1])) { - time = njs_date(&args[1])->time; - - } else if (njs_is_string(&args[1])) { - time = njs_date_string_parse(&args[1]); + if (njs_is_date(&args[1])) { + time = njs_date(&args[1])->time; - } else { - time = njs_number(&args[1]); - } + } else if (njs_is_string(&args[1])) { + time = njs_date_string_parse(&args[1]); } else { + time = njs_number(&args[1]); + } - time = NAN; - - njs_memzero(values, 8 * sizeof(int64_t)); + } else { - /* Day. */ - values[3] = 1; + time = NAN; - n = njs_min(8, nargs); + njs_memzero(values, 8 * sizeof(int64_t)); - for (i = 1; i < n; i++) { - if (!njs_is_numeric(&args[i])) { - ret = njs_value_to_numeric(vm, &args[i], &args[i]); - if (ret != NJS_OK) { - return ret; - } - } + /* Day. */ + values[3] = 1; - num = njs_number(&args[i]); + n = njs_min(8, nargs); - if (isnan(num) || isinf(num)) { - goto done; + for (i = 1; i < n; i++) { + if (!njs_is_numeric(&args[i])) { + ret = njs_value_to_numeric(vm, &args[i], &args[i]); + if (njs_slow_path(ret != NJS_OK)) { + return ret; } - - values[i] = num; } - /* Year. */ - if (values[1] >= 0 && values[1] < 100) { - values[1] += 1900; - } - - day = njs_make_day(values[1], values[2], values[3]); + num = njs_number(&args[i]); - tm = njs_make_time(values[4], values[5], values[6], values[7]); + if (isnan(num) || isinf(num)) { + goto done; + } - time = njs_make_date(day, tm, 1); + values[i] = num; } - done: - - date = njs_mp_alloc(vm->mem_pool, sizeof(njs_date_t)); - if (njs_slow_path(date == NULL)) { - njs_memory_error(vm); - return NJS_ERROR; + /* Year. */ + if (values[1] >= 0 && values[1] < 100) { + values[1] += 1900; } - njs_lvlhsh_init(&date->object.hash); - njs_lvlhsh_init(&date->object.shared_hash); - date->object.type = NJS_DATE; - date->object.shared = 0; - date->object.extensible = 1; - date->object.__proto__ = &vm->prototypes[NJS_OBJ_TYPE_DATE].object; + day = njs_make_day(values[1], values[2], values[3]); - date->time = njs_timeclip(time); + tm = njs_make_time(values[4], values[5], values[6], values[7]); - njs_set_date(&vm->retval, date); + time = njs_make_date(day, tm, 1); + } - return NJS_OK; +done: + + date = njs_mp_alloc(vm->mem_pool, sizeof(njs_date_t)); + if (njs_slow_path(date == NULL)) { + njs_memory_error(vm); + return NJS_ERROR; } - return njs_date_string(vm, "%a %b %d %Y %T GMT%z (%Z)", njs_gettime()); + njs_lvlhsh_init(&date->object.hash); + njs_lvlhsh_init(&date->object.shared_hash); + date->object.type = NJS_DATE; + date->object.shared = 0; + date->object.extensible = 1; + date->object.__proto__ = &vm->prototypes[NJS_OBJ_TYPE_DATE].object; + + date->time = njs_timeclip(time); + + njs_set_date(&vm->retval, date); + + return NJS_OK; } @@ -1081,7 +1087,7 @@ njs_date_prototype_to_string(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, return NJS_ERROR; } - return njs_date_string(vm, "%a %b %d %Y %T GMT%z (%Z)", + return njs_date_string(vm, &vm->retval, NJS_DATE_FMT_TO_STRING, njs_date(&args[0])->time); } @@ -1097,7 +1103,8 @@ njs_date_prototype_to_date_string(njs_vm_t *vm, njs_value_t *args, return NJS_ERROR; } - return njs_date_string(vm, "%a %b %d %Y", njs_date(&args[0])->time); + return njs_date_string(vm, &vm->retval, NJS_DATE_FMT_TO_DATE_STRING, + njs_date(&args[0])->time); } @@ -1112,77 +1119,108 @@ njs_date_prototype_to_time_string(njs_vm_t *vm, njs_value_t *args, return NJS_ERROR; } - return njs_date_string(vm, "%T GMT%z (%Z)", njs_date(&args[0])->time); + return njs_date_string(vm, &vm->retval, NJS_DATE_FMT_TO_TIME_STRING, + njs_date(&args[0])->time); } static njs_int_t -njs_date_string(njs_vm_t *vm, const char *fmt, double time) +njs_date_string(njs_vm_t *vm, njs_value_t *retval, njs_date_fmt_t fmt, + double time) { - size_t size; + u_char *p, sign; + int32_t year, tz; time_t clock; u_char buf[NJS_DATE_TIME_LEN]; struct tm tm; - if (!isnan(time)) { - clock = time / 1000; - localtime_r(&clock, &tm); + static const char *week[] = { "Sun", "Mon", "Tue", "Wed", + "Thu", "Fri", "Sat" }; - size = strftime((char *) buf, NJS_DATE_TIME_LEN, fmt, &tm); + static const char *month[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; - return njs_string_new(vm, &vm->retval, buf, size, size); + if (njs_slow_path(isnan(time))) { + vm->retval = njs_string_invalid_date; + return NJS_OK; } - vm->retval = njs_string_invalid_date; + p = buf; - return NJS_OK; -} + switch (fmt) { + case NJS_DATE_FMT_TO_ISO_STRING: + case NJS_DATE_FMT_TO_UTC_STRING: + clock = time / 1000; + gmtime_r(&clock, &tm); + year = tm.tm_year + 1900; + if (fmt == NJS_DATE_FMT_TO_UTC_STRING) { + p = njs_sprintf(p, buf + NJS_DATE_TIME_LEN, + "%s, %02d %s %04d %02d:%02d:%02d GMT", + week[tm.tm_wday], tm.tm_mday, month[tm.tm_mon], + year, tm.tm_hour, tm.tm_min, tm.tm_sec); -static njs_int_t -njs_date_prototype_to_utc_string(njs_vm_t *vm, njs_value_t *args, - njs_uint_t nargs, njs_index_t unused) -{ - double time; - time_t clock; - u_char buf[NJS_HTTP_DATE_TIME_LEN], *p; - struct tm tm; + break; + } - static const char *week[] = { "Sun", "Mon", "Tue", "Wed", - "Thu", "Fri", "Sat" }; + if (year >= 0 && year <= 9999) { + p = njs_sprintf(p, buf + NJS_DATE_TIME_LEN, "%04d", year); - static const char *month[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", - "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; + } else { + if (year > 0) { + *p++ = '+'; + } - if (njs_slow_path(!njs_is_date(&args[0]))) { - njs_type_error(vm, "cannot convert %s to date", - njs_type_string(args[0].type)); + p = njs_sprintf(p, buf + NJS_DATE_TIME_LEN, "%06d", year); + } - return NJS_ERROR; - } + p = njs_sprintf(p, buf + NJS_DATE_TIME_LEN, + "-%02d-%02dT%02d:%02d:%02d.%03dZ", + tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, + tm.tm_min, tm.tm_sec, + (int) ((int64_t) time % 1000)); - time = njs_date(&args[0])->time; + break; - if (!isnan(time)) { + case NJS_DATE_FMT_TO_TIME_STRING: + case NJS_DATE_FMT_TO_DATE_STRING: + case NJS_DATE_FMT_TO_STRING: + default: clock = time / 1000; - gmtime_r(&clock, &tm); + localtime_r(&clock, &tm); - p = njs_sprintf(buf, buf + NJS_HTTP_DATE_TIME_LEN, - "%s, %02d %s %4d %02d:%02d:%02d GMT", - week[tm.tm_wday], tm.tm_mday, month[tm.tm_mon], - tm.tm_year + 1900, tm.tm_hour, tm.tm_min, tm.tm_sec); + if (fmt != NJS_DATE_FMT_TO_TIME_STRING) { + p = njs_sprintf(p, buf + NJS_DATE_TIME_LEN, + "%s %s %02d %04d", + week[tm.tm_wday], month[tm.tm_mon], tm.tm_mday, + tm.tm_year + 1900); + } - return njs_string_new(vm, &vm->retval, buf, p - buf, p - buf); - } + if (fmt != NJS_DATE_FMT_TO_DATE_STRING) { + tz = -njs_tz_offset(time); + sign = (tz < 0) ? '-' : '+'; + + if (tz < 0) { + tz = -tz; + } - vm->retval = njs_string_invalid_date; + if (p != buf) { + *p++ = ' '; + } - return NJS_OK; + p = njs_sprintf(p, buf + NJS_DATE_TIME_LEN, + "%02d:%02d:%02d GMT%c%02d%02d", + tm.tm_hour, tm.tm_min, tm.tm_sec, + sign, tz / 60, tz % 60); + } + } + + return njs_string_new(vm, retval, buf, p - buf, p - buf); } static njs_int_t -njs_date_prototype_to_iso_string(njs_vm_t *vm, njs_value_t *args, +njs_date_prototype_to_utc_string(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused) { if (njs_slow_path(!njs_is_date(&args[0]))) { @@ -1192,6 +1230,15 @@ njs_date_prototype_to_iso_string(njs_vm_t *vm, njs_value_t *args, return NJS_ERROR; } + return njs_date_string(vm, &vm->retval, NJS_DATE_FMT_TO_UTC_STRING, + njs_date(&args[0])->time); +} + + +static njs_int_t +njs_date_prototype_to_iso_string(njs_vm_t *vm, njs_value_t *args, + njs_uint_t nargs, njs_index_t unused) +{ return njs_date_to_string(vm, &vm->retval, &args[0]); } @@ -1199,33 +1246,15 @@ njs_date_prototype_to_iso_string(njs_vm_t *vm, njs_value_t *args, njs_int_t njs_date_to_string(njs_vm_t *vm, njs_value_t *retval, const njs_value_t *date) { - int32_t year; - double time; - time_t clock; - u_char buf[NJS_ISO_DATE_TIME_LEN], *p; - struct tm tm; - - time = njs_date(date)->time; - - if (!isnan(time)) { - clock = time / 1000; - - gmtime_r(&clock, &tm); - - year = tm.tm_year + 1900; - - p = njs_sprintf(buf, buf + NJS_ISO_DATE_TIME_LEN, - (year < 0) ? "%07d-%02d-%02dT%02d:%02d:%02d.%03dZ" - : "%04d-%02d-%02dT%02d:%02d:%02d.%03dZ", - year, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, - tm.tm_sec, (int) ((int64_t) time % 1000)); + if (njs_slow_path(!njs_is_date(date))) { + njs_type_error(vm, "cannot convert %s to date", + njs_type_string(date->type)); - return njs_string_new(vm, retval, buf, p - buf, p - buf); + return NJS_ERROR; } - *retval = njs_string_invalid_date; - - return NJS_OK; + return njs_date_string(vm, retval, NJS_DATE_FMT_TO_ISO_STRING, + njs_date(date)->time); } diff --git a/src/test/njs_unit_test.c b/src/test/njs_unit_test.c index 649bdaa4..0872dcc6 100644 --- a/src/test/njs_unit_test.c +++ b/src/test/njs_unit_test.c @@ -11572,6 +11572,9 @@ static njs_unit_test_t njs_test[] = { njs_str("(new Date(86400)).getTime()"), njs_str("86400") }, + { njs_str("Date().split(' ')[0] in {'Mon':1, 'Tue':1, 'Wed':1, 'Thu':1, 'Fri':1, 'Sat':1, 'Sun':1}"), + njs_str("true") }, + { njs_str("var d = new Date(''); d +' '+ d.getTime()"), njs_str("Invalid Date NaN") }, @@ -11619,9 +11622,38 @@ static njs_unit_test_t njs_test[] = "local.toISOString()"), njs_str("1999-10-10T10:10:10.010Z") }, -#if 0 +#if 0 /* FIXME: implement own gmtime_r(). */ /* These tests fail on Solaris: gmtime_r() returns off by one day. */ + { njs_str("[" + "'-010000-01-01T00:00:00.000Z'," + "'+010000-01-01T00:00:00.000Z'," + "'0002-01-01T00:00:00.000Z'," + "'0123-01-01T00:00:00.000Z'," + "].every((iso)=> (new Date(iso)).toISOString() === iso)"), + njs_str("true") }, + + { njs_str("new Date('0020-01-01T00:00:00Z').toUTCString()"), + njs_str("Wed, 01 Jan 0020 00:00:00 GMT") }, + + { njs_str("new Date('0020-01-01T00:00:00Z').toString().slice(0, 15)"), + njs_str("Wed Jan 01 0020") }, + + { njs_str("(new Date('-000001-07-01T00:00Z')).toUTCString()"), + njs_str("Thu, 01 Jul -0001 00:00:00 GMT") }, + + { njs_str("(new Date('-000012-07-01T00:00Z')).toUTCString()"), + njs_str("Fri, 01 Jul -0012 00:00:00 GMT") }, + + { njs_str("(new Date('-000123-07-01T00:00Z')).toUTCString()"), + njs_str("Sun, 01 Jul -0123 00:00:00 GMT") }, + + { njs_str("(new Date('-001234-07-01T00:00Z')).toUTCString()"), + njs_str("Fri, 01 Jul -1234 00:00:00 GMT") }, + + { njs_str("(new Date('-012345-07-01T00:00Z')).toUTCString()"), + njs_str("Thu, 01 Jul -12345 00:00:00 GMT") }, + { njs_str("var d = new Date(-62167219200000); d.toISOString()"), njs_str("0000-01-01T00:00:00.000Z") }, -- 2.47.3