diff --git a/Cargo.toml b/Cargo.toml index 812552d..5ba2515 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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", diff --git a/libquickjs-sys/embed/quickjs/quickjs.c b/libquickjs-sys/embed/quickjs/quickjs.c index 68f0120..a3325b3 100644 --- a/libquickjs-sys/embed/quickjs/quickjs.c +++ b/libquickjs-sys/embed/quickjs/quickjs.c @@ -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); diff --git a/src/lib.rs b/src/lib.rs index 9c5bb82..aeaeab5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -32,7 +32,6 @@ //! "#).unwrap(); //! ``` -#![allow(dead_code)] #![deny(missing_docs)] mod bindings; diff --git a/src/tests.rs b/src/tests.rs index b35900b..a6a4773 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -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); -}