Compare commits

..

No commits in common. "6eeb0f70b9d70a3728baa7d4bbdc3fe81d520206" and "f13cc55ce2d0f01db66bf91c3cd272ab5914a009" have entirely different histories.

4 changed files with 91 additions and 291 deletions

View file

@ -26,9 +26,6 @@ num-traits = { version = "0.2.0", optional = true }
log = { version = "0.4.8", optional = true }
once_cell = "1.2.0"
[dev-dependencies]
rstest = "0.15.0"
[workspace]
members = [
"libquickjs-sys",

View file

@ -1436,7 +1436,7 @@ static inline int is_digit(int c) {
}
static inline int is_space_like(char c) {
return c == ' ' || c == ',' || c == ':' || c == '-' || c == '/';
return c == ' ' || c == ',' || c == ':' || c == '-';
}
typedef struct JSClassShortDef {
@ -48322,7 +48322,7 @@ static void string_skip_spaces_and_comments(JSString *sp, int *pp) {
int nxt = *pp + 1;
// interpret - before a number as a sign rather than a comment char
if (ch == '-' && nesting == 0 && nxt < sp->len && is_digit(string_get(sp, nxt))) {
if (ch == '-' && nxt < sp->len && is_digit(string_get(sp, nxt))) {
break;
}
if (!is_space_like(ch)) {
@ -48339,10 +48339,12 @@ static void string_skip_spaces_and_comments(JSString *sp, int *pp) {
}
}
static inline BOOL char_eq_ignorecase(char c1, char c2) {
if ((c1 >= 'A' && c1 <= 'Z') || (c1 >= 'a' && c1 <= 'z'))
return (c1 | 0x20) == (c2 | 0x20);
return c1 == c2;
static BOOL char_eq_ignorecase(char c1, char c2) {
if (c1 == c2) return TRUE;
if (c1 >= 'A' && c1 <= 'Z' && c2 == c1 + 32) return TRUE;
if (c1 >= 'a' && c1 <= 'z' && c2 == c1 - 32) return TRUE;
return FALSE;
}
static BOOL string_eq_ignorecase(JSString *s1, int p, const char *s2, int len) {
@ -48440,34 +48442,6 @@ static int string_get_milliseconds(JSString *sp, int *pp, int64_t *pval) {
return 0;
}
static int string_get_num_timezone(JSString *sp, int *pp, int64_t *pval)
{
int p = *pp;
int64_t o;
if (string_get_signed_digits(sp, &p, &o))
return -1;
if (o < -9959 || o > 9959) {
return -1;
}
int sgn = (o < 0) ? -1 : 1;
o = abs((int32_t) o);
if (string_get(sp, p) != ':') {
*pval = ((o / 100) * 60 + (o % 100)) * sgn;
} else {
p++;
int64_t o2;
if (string_get_digits(sp, &p, &o2))
return -1;
*pval = (o * 60 + o2) * sgn;
}
*pp = p;
return 0;
}
static int find_abbrev(JSString *sp, int p, const char *list, int count) {
int n, i;
@ -48522,18 +48496,7 @@ static JSValue js_Date_parse(JSContext *ctx, JSValueConst this_val,
sp = JS_VALUE_GET_STRING(s);
p = 0;
string_skip_spaces_and_comments(sp, &p);
int end_of_digits = p;
if (string_get(sp, end_of_digits) == '+' || string_get(sp, end_of_digits) == '-') {
p++;
}
while (end_of_digits < sp->len && is_digit(string_get(sp, end_of_digits))) {
end_of_digits++;
}
if ((end_of_digits - p) > 0 &&
(string_get(sp, end_of_digits) == '-' || string_get(sp, end_of_digits) == 'T')) {
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]))
@ -48543,28 +48506,23 @@ static JSValue js_Date_parse(JSContext *ctx, JSValueConst this_val,
if (p >= sp->len)
break;
switch(i) {
case 1: // Year
case 2: // Month
case 1:
case 2:
c = '-';
break;
case 3: // Day
case 3:
c = 'T';
break;
case 4: // Hour
case 5: // Minute
case 4:
case 5:
c = ':';
break;
case 6: // Second
case 6:
c = '.';
break;
}
if (string_get(sp, p) != c) {
// 2000T08:00Z
if (i < 3 && string_get(sp, p) == 'T') {
i = 3;
}
else break;
}
if (string_get(sp, p) != c)
break;
p++;
if (i == 6) {
if (string_get_milliseconds(sp, &p, &fields[i]))
@ -48574,9 +48532,6 @@ static JSValue js_Date_parse(JSContext *ctx, JSValueConst this_val,
goto done;
}
}
// Hour only is invalid
if (i == 4) goto done;
/* no time: UTC by default */
is_local = (i > 3);
fields[1] -= 1;
@ -48624,7 +48579,7 @@ static JSValue js_Date_parse(JSContext *ctx, JSValueConst this_val,
if (p - word_start >= 3) {
month = find_abbrev(sp, word_start, month_names, 12);
}
string_skip_spaces_and_comments(sp, &p);
string_skip_spaces_and_comments(sp, &p); // and comments
word_start = p;
} else {
p++;
@ -48638,7 +48593,7 @@ static JSValue js_Date_parse(JSContext *ctx, JSValueConst this_val,
month = find_abbrev(sp, word_start, month_names, 12);
}
string_skip_spaces_and_comments(sp, &p);
string_skip_spaces_and_comments(sp, &p); // and comments
if (sp->len <= p)
goto done;
@ -48647,7 +48602,7 @@ static JSValue js_Date_parse(JSContext *ctx, JSValueConst this_val,
if (string_get_digits(sp, &p, &day))
goto done;
int64_t year = -1;
int64_t year = 0;
if (day > 31) {
if (string_get(sp, p) != '/')
goto done;
@ -48676,7 +48631,7 @@ static JSValue js_Date_parse(JSContext *ctx, JSValueConst this_val,
if (string_get_digits(sp, &p, &day))
goto done;
if (!is_space_like(string_get(sp, p)))
if (string_get(sp, p) != '/')
goto done;
p++;
if (sp->len <= p)
@ -48685,31 +48640,40 @@ static JSValue js_Date_parse(JSContext *ctx, JSValueConst this_val,
if (string_get(sp, p) == '-') {
p++;
}
string_skip_spaces_and_comments(sp, &p);
string_skip_spaces_and_comments(sp, &p); // and comments
if (string_get(sp, p) == ',') {
p++;
}
// Jan,2000,08:00:00 UT
if (month == -1) {
month = find_abbrev(sp, p, month_names, 12);
if (month == -1)
goto done;
while (p < sp->len && !is_space_like(string_get(sp, p))) {
while (p < sp->len && string_get(sp, p) != '-' && string_get(sp, p) != ' ') {
p++;
}
if (sp->len <= p)
goto done;
// '-99 23:12:40 GMT'
if (string_get(sp, p) != '-' && string_get(sp, p) != '/' && string_get(sp, p) != ' ') {
goto done;
}
p++;
}
if (month < 0 || month > 11) {
goto done;
}
}
string_skip_spaces_and_comments(sp, &p);
if (year < 0) {
// Year following, e.g. 01 Jan 2000 08:00:00 UT
// Time following, e.g. Jan 01 08:00:00 UT 2000
if (sp->len <= p + 2 || string_get(sp, p + 2) != ':') {
if (string_get_digits(sp, &p, &year))
goto done;
if (year <= 0) {
if (string_get_digits(sp, &p, &year))
goto done;
if (year <= 0) {
goto done;
}
}
@ -48725,7 +48689,9 @@ static JSValue js_Date_parse(JSContext *ctx, JSValueConst this_val,
year = -1;
} else if (is_space_like(string_get(sp, p))) {
p++;
string_skip_spaces_and_comments(sp, &p);
string_skip_spaces_and_comments(sp, &p); // and comments
} else {
goto done;
}
// Read a number? If not, this might be a timezone name.
@ -48745,6 +48711,10 @@ static JSValue js_Date_parse(JSContext *ctx, JSValueConst this_val,
if (string_get_digits(sp, &p, &minute))
goto done;
if (minute < 0 || minute > 59) {
goto done;
}
// ':40 GMT'
// seconds are optional in rfc822 + rfc2822
@ -48753,6 +48723,10 @@ static JSValue js_Date_parse(JSContext *ctx, JSValueConst this_val,
if (string_get_digits(sp, &p, &second))
goto done;
if (second < 0 || second > 59) {
goto done;
}
// disallow trailing colon seconds
if (string_get(sp, p) == ':') {
@ -48762,7 +48736,7 @@ static JSValue js_Date_parse(JSContext *ctx, JSValueConst this_val,
goto done;
}
string_skip_spaces_and_comments(sp, &p);
string_skip_spaces_and_comments(sp, &p); // and comments
if (string_eq_ignorecase(sp, p, "AM", 2)) {
if (hour > 12) {
@ -48772,7 +48746,7 @@ static JSValue js_Date_parse(JSContext *ctx, JSValueConst this_val,
hour = 0;
}
p += 2;
string_skip_spaces_and_comments(sp, &p);
string_skip_spaces_and_comments(sp, &p); // and comments
} else if (string_eq_ignorecase(sp, p, "PM", 2)) {
if (hour > 12) {
goto done;
@ -48781,7 +48755,7 @@ static JSValue js_Date_parse(JSContext *ctx, JSValueConst this_val,
hour += 12;
}
p += 2;
string_skip_spaces_and_comments(sp, &p);
string_skip_spaces_and_comments(sp, &p); // and comments
}
}
}
@ -48790,8 +48764,26 @@ static JSValue js_Date_parse(JSContext *ctx, JSValueConst this_val,
// Some websites omit the time zone
if (sp->len > p) {
if (string_get(sp, p) == '+' || string_get(sp, p) == '-') {
if (string_get_num_timezone(sp, &p, &tz))
int64_t o;
if (string_get_digits(sp, &p, &o))
goto done;
if (o < -9959 || o > 9959) {
goto done;
}
int sgn = (o < 0) ? -1 : 1;
o = abs((int32_t) o);
if (string_get(sp, p) != ':') {
tz = ((o / 100) * 60 + (o % 100)) * sgn;
} else {
p++;
int64_t o2;
if (string_get_digits(sp, &p, &o2))
goto done;
tz = (o * 60 + o2) * sgn;
}
is_local = FALSE;
} else {
for (int i = 0; i < sizeof(known_zones) / sizeof(struct KnownZone); i++) {
@ -48799,15 +48791,6 @@ static JSValue js_Date_parse(JSContext *ctx, JSValueConst this_val,
tz = known_zones[i].tzOffset;
p += strlen(known_zones[i].tzName);
is_local = FALSE;
// TZ offset (GMT+0)
if (string_get(sp, p) == '+' || string_get(sp, p) == '-') {
int64_t o;
if (string_get_num_timezone(sp, &p, &o))
goto done;
tz += o;
}
break;
}
}
@ -48845,16 +48828,6 @@ static JSValue js_Date_parse(JSContext *ctx, JSValueConst this_val,
fields[5] = second;
}
// Validate fields
if (fields[1] < 0 || fields[1] > 11 ||
fields[2] < 1 || fields[2] > 31 ||
fields[3] < 0 || fields[3] > 24 ||
fields[4] < 0 || fields[4] > 59 ||
fields[5] < 0 || fields[5] > 59 ||
fields[6] < 0 || fields[6] > 999 ||
fields[3] * 3600 * 1000 + fields[4] * 60000 + fields[5] * 1000 + fields[6] > 24 * 3600 * 1000
) goto done;
for(i = 0; i < 7; i++)
fields1[i] = fields[i];
d = set_date_fields(fields1, is_local) - (tz * 60000);

View file

@ -32,7 +32,6 @@
//! "#).unwrap();
//! ```
#![allow(dead_code)]
#![deny(missing_docs)]
mod bindings;

View file

@ -1,6 +1,7 @@
use std::{collections::HashMap, convert::TryInto};
use rstest::rstest;
#[cfg(feature = "chrono")]
use chrono::{Date, DateTime, Local, NaiveDate, NaiveDateTime, NaiveTime, Utc};
use super::*;
@ -625,162 +626,9 @@ fn test_global_setter() {
ctx.eval("a + 1").unwrap();
}
// Test suite was taken from V8
// Source: https://github.com/v8/v8/blob/9433ad119aadfe10d60935029195c31f25ab8624/test/mjsunit/date-parse.js
#[rstest]
// testCasesES5Misc
#[case("2000-01-01T08:00:00.000Z", 946713600000)]
#[case("2000-01-01T08:00:00Z", 946713600000)]
#[case("2000-01-01T08:00Z", 946713600000)]
#[case("2000-01T08:00:00.000Z", 946713600000)]
#[case("2000T08:00:00.000Z", 946713600000)]
#[case("2000T08:00Z", 946713600000)]
#[case("2000-01T00:00:00.000-08:00", 946713600000)]
#[case("2000-01T08:00:00.001Z", 946713600001)]
#[case("2000-01T08:00:00.099Z", 946713600099)]
#[case("2000-01T08:00:00.999Z", 946713600999)]
#[case("2000-01T00:00:00.001-08:00", 946713600001)]
#[case("2000-01-01T24:00Z", 946771200000)]
#[case("2000-01-01T24:00:00Z", 946771200000)]
#[case("2000-01-01T24:00:00.000Z", 946771200000)]
fn test_date_iso(#[case] input: &str, #[case] expect: i64) {
let ctx = Context::new().unwrap();
let res = ctx
.eval(&format!(r#"new Date("{}").getTime()"#, input))
.unwrap();
let n: f64 = res.try_into().unwrap();
assert_eq!(n, expect as f64);
}
#[rstest]
#[case("Sat, 01-Jan-2000 08:00:00 GMT", 946713600000)]
#[case("Sat, 01-Jan-2000 08:00:00 GMT+0", 946713600000)]
#[case("Sat, 01-Jan-2000 08:00:00 GMT+00", 946713600000)]
#[case("Sat, 01-Jan-2000 08:00:00 GMT+000", 946713600000)]
#[case("Sat, 01-Jan-2000 08:00:00 GMT+0000", 946713600000)]
#[case("Sat, 01-Jan-2000 08:00:00 GMT+00:00", 946713600000)]
#[case("Sat, 01 Jan 2000 08:00:00 GMT", 946713600000)]
#[case("Saturday, 01-Jan-00 08:00:00 GMT", 946713600000)]
#[case("01 Jan 00 08:00 -0000", 946713600000)]
#[case("01 Jan 00 08:00 +0000", 946713600000)]
fn test_date_gmt(#[case] input: &str, #[case] expect: i64) {
let ctx = Context::new().unwrap();
let res = ctx
.eval(&format!(r#"new Date("{}").getTime()"#, input))
.unwrap();
let n: f64 = res.try_into().unwrap();
assert_eq!(n, expect as f64);
}
#[rstest]
// EST (-5:00)
#[case("Sat, 01-Jan-2000 03:00:00 UTC-0500", 946713600000)]
#[case("Sat, 01-Jan-2000 03:00:00 UTC-05:00", 946713600000)]
#[case("Sat, 01-Jan-2000 03:00:00 EST", 946713600000)]
#[case("Sat, 01 Jan 2000 03:00:00 EST", 946713600000)]
#[case("Saturday, 01-Jan-00 03:00:00 EST", 946713600000)]
#[case("01 Jan 00 03:00 -0500", 946713600000)]
// EDT (-4:00)
#[case("Sat, 01-Jan-2000 04:00:00 EDT", 946713600000)]
#[case("Sat, 01 Jan 2000 04:00:00 EDT", 946713600000)]
#[case("Saturday, 01-Jan-00 04:00:00 EDT", 946713600000)]
#[case("01 Jan 00 04:00 -0400", 946713600000)]
// CST (-6:00)
#[case("Sat, 01-Jan-2000 02:00:00 CST", 946713600000)]
#[case("Sat, 01 Jan 2000 02:00:00 CST", 946713600000)]
#[case("Saturday, 01-Jan-00 02:00:00 CST", 946713600000)]
#[case("01 Jan 00 02:00 -0600", 946713600000)]
// CDT (-5:00)
#[case("Sat, 01-Jan-2000 03:00:00 CDT", 946713600000)]
#[case("Sat, 01 Jan 2000 03:00:00 CDT", 946713600000)]
#[case("Saturday, 01-Jan-00 03:00:00 CDT", 946713600000)]
#[case("01 Jan 00 03:00 -0500", 946713600000)]
// MST (-7:00)
#[case("Sat, 01-Jan-2000 01:00:00 MST", 946713600000)]
#[case("Sat, 01 Jan 2000 01:00:00 MST", 946713600000)]
#[case("Saturday, 01-Jan-00 01:00:00 MST", 946713600000)]
#[case("01 Jan 00 01:00 -0700", 946713600000)]
// MDT (-6:00)
#[case("Sat, 01-Jan-2000 02:00:00 MDT", 946713600000)]
#[case("Sat, 01 Jan 2000 02:00:00 MDT", 946713600000)]
#[case("Saturday, 01-Jan-00 02:00:00 MDT", 946713600000)]
#[case("01 Jan 00 02:00 -0600", 946713600000)]
// PST (-8:00)
#[case("Sat, 01-Jan-2000 00:00:00 PST", 946713600000)]
#[case("Sat, 01 Jan 2000 00:00:00 PST", 946713600000)]
#[case("Saturday, 01-Jan-00 00:00:00 PST", 946713600000)]
#[case("01 Jan 00 00:00 -0800", 946713600000)]
#[case("Sat, 01-Jan-2000 PST", 946713600000)]
// PDT (-7:00)
#[case("Sat, 01-Jan-2000 01:00:00 PDT", 946713600000)]
#[case("Sat, 01 Jan 2000 01:00:00 PDT", 946713600000)]
#[case("Saturday, 01-Jan-00 01:00:00 PDT", 946713600000)]
#[case("01 Jan 00 01:00 -0700", 946713600000)]
fn test_date_tz(#[case] input: &str, #[case] expect: i64) {
let ctx = Context::new().unwrap();
let res = ctx
.eval(&format!(r#"new Date("{}").getTime()"#, input))
.unwrap();
let n: f64 = res.try_into().unwrap();
assert_eq!(n, expect as f64);
}
#[rstest]
// Special handling for years in the [0, 100) range.
#[case("Sat, 01 Jan 0 08:00:00 UT", 946713600000)] // year 2000
#[case("Sat, 01 Jan 49 08:00:00 UT", 2493100800000)] // year 2049
#[case("Sat, 01 Jan 50 08:00:00 UT", -631123200000)] // year 1950
#[case("Sat, 01 Jan 99 08:00:00 UT", 915177600000)] // year 1999
#[case("Sat, 01 Jan 100 08:00:00 UT", -59011430400000)] // year 100
// Test PM after time.
#[case("Sat, 01-Jan-2000 08:00 PM UT", 946756800000)]
#[case("Sat, 01 Jan 2000 08:00 PM UT", 946756800000)]
#[case("Jan 01 2000 08:00 PM UT", 946756800000)]
#[case("Jan 01 08:00 PM UT 2000", 946756800000)]
#[case("Saturday, 01-Jan-00 08:00 PM UT", 946756800000)]
#[case("01 Jan 00 08:00 PM +0000", 946756800000)]
fn test_date_special(#[case] input: &str, #[case] expect: i64) {
let ctx = Context::new().unwrap();
let res = ctx
.eval(&format!(r#"new Date("{}").getTime()"#, input))
.unwrap();
let n: f64 = res.try_into().unwrap();
assert_eq!(n, expect as f64);
}
#[rstest]
#[case("2000-01-01TZ")]
#[case("2000-01-01T60Z")]
#[case("2000-01-01T60:60Z")]
#[case("2000-01-0108:00Z")]
#[case("2000-01-01T08Z")]
#[case("2000-01-01T24:01")]
#[case("2000-01-01T24:00:01")]
#[case("2000-01-01T24:00:00.001")]
#[case("2000-01-01T24:00:00.999Z")]
#[case("May 25 2008 1:30 (PM)) UTC")]
#[case("May 25 2008 1:30( )AM (PM)")]
#[case("a1")]
#[case("nasfdjklsfjoaifg1")]
#[case("x_2")]
#[case("May 25 2008 AAA (GMT)")]
fn test_date_invalid(#[case] input: &str) {
let ctx = Context::new().unwrap();
let res = ctx
.eval(&format!(r#"new Date("{}").getTime()"#, input))
.unwrap();
let n: f64 = res.try_into().unwrap();
assert!(n.is_nan(), "got: {}", n);
}
#[cfg(feature = "chrono")]
#[test]
fn test_date_ut() {
fn test_ut() {
let test_cases_ut = vec![
"Sat, 01-Jan-2000 08:00:00 UT",
"Sat, 01 Jan 2000 08:00:00 UT",
@ -850,20 +698,27 @@ fn test_date_ut() {
" 01 Jan 00 08:00 +0000()((asfd)(Sat, 01-Jan-2000)) ",
];
let expected = JsValue::Date(DateTime::from_utc(
NaiveDateTime::new(
NaiveDate::from_ymd(2000, 1, 1),
NaiveTime::from_hms(8, 0, 0),
),
Utc,
));
let mut passed = 0;
let mut failed = 0;
for case in test_cases_ut {
let ctx = Context::new().unwrap();
let res = ctx
.eval(&format!(r#"new Date("{}").getTime()"#, case))
.eval(&format!(r#"new Date("{}")"#, case))
.unwrap();
let n: f64 = res.try_into().unwrap();
if n == 946713600000.0 {
if res == expected {
passed += 1;
} else {
println!("FAIL: `{}` - {}", case, n);
println!("FAIL: `{}` - {:?}", case, res);
failed += 1;
}
}
@ -871,27 +726,3 @@ fn test_date_ut() {
println!("{}/{} Passed", passed, passed + failed);
assert_eq!(failed, 0);
}
#[test]
// Test if the JS interpreter can parse its own date format
// (Dates from 1970 to ~2070 with 150h steps.)
fn test_date_selfparse() {
let ctx = Context::new().unwrap();
let res = ctx
.eval(
r#"
test = () => {
for (var i = 0; i < 24 * 365 * 100; i += 150) {
var ms = i * (3600 * 1000);
var s = (new Date(ms)).toString();
if(ms != Date.parse(s)) return false;
}
return true;
}
test();
"#,
)
.unwrap();
let res: bool = res.try_into().unwrap();
assert!(res);
}