From 45e0bdbe198d6f46eb17276bb8848e609c3ea3c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20F=C3=A4rber?= <01mf02@gmail.com> Date: Tue, 30 Jul 2024 08:58:54 +0200 Subject: [PATCH] Use `chrono` instead of `time`. --- Cargo.lock | 53 +++++++++++++++++++---------------------- jaq-core/Cargo.toml | 3 ++- jaq-core/src/time.rs | 48 +++++++++++++------------------------ jaq-core/tests/tests.rs | 35 +++++++++++++-------------- jaq-std/tests/std.rs | 47 ++++++++++++++++++------------------ 5 files changed, 83 insertions(+), 103 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d74ebda72..33a76d2ff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -35,6 +35,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "autocfg" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + [[package]] name = "base64" version = "0.22.0" @@ -65,6 +71,15 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +dependencies = [ + "num-traits", +] + [[package]] name = "clap" version = "4.0.22" @@ -240,6 +255,7 @@ version = "1.5.0" dependencies = [ "aho-corasick", "base64", + "chrono", "hifijson", "jaq-interpret", "jaq-syn", @@ -247,7 +263,6 @@ dependencies = [ "log", "regex", "serde_json", - "time", "urlencoding", ] @@ -362,6 +377,15 @@ dependencies = [ "libmimalloc-sys", ] +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + [[package]] name = "once_cell" version = "1.18.0" @@ -551,33 +575,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "time" -version = "0.3.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890" -dependencies = [ - "itoa", - "serde", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" - -[[package]] -name = "time-macros" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd80a657e71da814b8e5d60d3374fc6d35045062245d80224748ae522dd76f36" -dependencies = [ - "time-core", -] - [[package]] name = "unicode-ident" version = "1.0.10" diff --git a/jaq-core/Cargo.toml b/jaq-core/Cargo.toml index 5978da5df..acd174807 100644 --- a/jaq-core/Cargo.toml +++ b/jaq-core/Cargo.toml @@ -16,11 +16,12 @@ std = [] format = ["aho-corasick", "base64", "urlencoding"] math = ["libm"] parse_json = ["hifijson"] +time = ["chrono"] [dependencies] jaq-interpret = { version = "1.5.0", path = "../jaq-interpret" } hifijson = { version = "0.2.0", optional = true } -time = { version = "0.3.20", optional = true, features = ["formatting", "parsing"] } +chrono = { version = "0.4.38", default-features = false, features = ["alloc"], optional = true } regex = { version = "1.9", optional = true } log = { version = "0.4.17", optional = true } libm = { version = "0.2.7", optional = true } diff --git a/jaq-core/src/time.rs b/jaq-core/src/time.rs index fee573073..97edbf5f3 100644 --- a/jaq-core/src/time.rs +++ b/jaq-core/src/time.rs @@ -1,50 +1,36 @@ use crate::{ValR2, ValT}; use alloc::string::{String, ToString}; +use chrono::DateTime; use jaq_interpret::Error; -/// Parse an ISO-8601 timestamp string to a number holding the equivalent UNIX timestamp +/// Parse an ISO 8601 timestamp string to a number holding the equivalent UNIX timestamp /// (seconds elapsed since 1970/01/01). +/// +/// Actually, this parses RFC 3339; see +/// for differences. +/// jq also only parses a very restricted subset of ISO 8601. pub fn from_iso8601(s: &str) -> ValR2 { - use time::format_description::well_known::Iso8601; - use time::OffsetDateTime; - let datetime = OffsetDateTime::parse(s, &Iso8601::DEFAULT) + let dt = DateTime::parse_from_rfc3339(s) .map_err(|e| Error::str(format_args!("cannot parse {s} as ISO-8601 timestamp: {e}")))?; - let epoch_s = datetime.unix_timestamp(); if s.contains('.') { - let seconds = epoch_s as f64 + (f64::from(datetime.nanosecond()) * 1e-9_f64); - Ok(seconds.into()) + Ok((dt.timestamp_micros() as f64 * 1e-6_f64).into()) } else { - isize::try_from(epoch_s) + let seconds = dt.timestamp(); + isize::try_from(seconds) .map(V::from) - .or_else(|_| V::from_num(&epoch_s.to_string())) + .or_else(|_| V::from_num(&seconds.to_string())) } } -/// Format a number as an ISO-8601 timestamp string. +/// Format a number as an ISO 8601 timestamp string. pub fn to_iso8601(v: &V) -> Result> { - use time::format_description::well_known::iso8601; - use time::OffsetDateTime; - const SECONDS_CONFIG: iso8601::EncodedConfig = iso8601::Config::DEFAULT - .set_time_precision(iso8601::TimePrecision::Second { - decimal_digits: None, - }) - .encode(); - - let fail1 = |e| Error::str(format_args!("cannot format {v} as ISO-8601 timestamp: {e}")); - let fail2 = |e| Error::str(format_args!("cannot format {v} as ISO-8601 timestamp: {e}")); - + let fail = || Error::str(format_args!("cannot format {v} as ISO-8601 timestamp")); if let Some(i) = v.as_isize() { - let iso8601_fmt_s = iso8601::Iso8601::; - OffsetDateTime::from_unix_timestamp(i as i64) - .map_err(fail1)? - .format(&iso8601_fmt_s) - .map_err(fail2) + let dt = DateTime::from_timestamp(i as i64, 0).ok_or_else(fail)?; + Ok(dt.format("%Y-%m-%dT%H:%M:%SZ").to_string()) } else { let f = v.as_f64()?; - let f_ns = (f * 1_000_000_000_f64).round() as i128; - OffsetDateTime::from_unix_timestamp_nanos(f_ns) - .map_err(fail1)? - .format(&iso8601::Iso8601::DEFAULT) - .map_err(fail2) + let dt = DateTime::from_timestamp_micros((f * 1e6_f64) as i64).ok_or_else(fail)?; + Ok(dt.format("%Y-%m-%dT%H:%M:%S%.fZ").to_string()) } } diff --git a/jaq-core/tests/tests.rs b/jaq-core/tests/tests.rs index 4e9717ed9..1bfcd539a 100644 --- a/jaq-core/tests/tests.rs +++ b/jaq-core/tests/tests.rs @@ -37,25 +37,22 @@ fn ascii() { give(json!("aAaAäの"), "ascii_downcase", json!("aaaaäの")); } -#[test] -fn dateiso8601() { - give( - json!("1970-01-02T00:00:00Z"), - "fromdateiso8601", - json!(86400), - ); - give( - json!("1970-01-02T00:00:00.123456789Z"), - "fromdateiso8601", - json!(86400.123456789), - ); - give(json!(86400), "todateiso8601", json!("1970-01-02T00:00:00Z")); - give( - json!(86400.123456789), - "todateiso8601", - json!("1970-01-02T00:00:00.123456789Z"), - ); -} +yields!( + fromdate, + r#""1970-01-02T00:00:00Z" | fromdateiso8601"#, + 86400 +); +yields!( + fromdate_micros, + r#""1970-01-02T00:00:00.123456Z" | fromdateiso8601"#, + 86400.123456 +); +yields!(todate, r#"86400 | todateiso8601"#, "1970-01-02T00:00:00Z"); +yields!( + todate_micros, + r#"86400.123456 | todateiso8601"#, + "1970-01-02T00:00:00.123456Z" +); #[test] fn explode_implode() { diff --git a/jaq-std/tests/std.rs b/jaq-std/tests/std.rs index 85fe0a1a9..a404dacca 100644 --- a/jaq-std/tests/std.rs +++ b/jaq-std/tests/std.rs @@ -25,34 +25,33 @@ fn any() { give(json!({"a": false, "b": true}), "any", json!(true)); } -#[test] -fn date() { - // aliases for fromdateiso8601 and todateiso8601 - give(json!("1970-01-02T00:00:00Z"), "fromdate", json!(86400)); - give( - json!("1970-01-02T00:00:00.123456789Z"), - "fromdate", - json!(86400.123456789), - ); - give(json!(86400), "todate", json!("1970-01-02T00:00:00Z")); - give( - json!(86400.123456789), - "todate", - json!("1970-01-02T00:00:00.123456789Z"), - ); -} +// aliases for fromdateiso8601 and todateiso8601 +yields!(fromdate, r#""1970-01-02T00:00:00Z" | fromdate"#, 86400); +yields!( + fromdate_mu, + r#""1970-01-02T00:00:00.123456Z" | fromdate"#, + 86400.123456 +); +yields!(todate, r#"86400 | todate"#, "1970-01-02T00:00:00Z"); +yields!( + todate_mu, + r#"86400.123456 | todate"#, + "1970-01-02T00:00:00.123456Z" +); -#[test] -fn date_roundtrip() { - let epoch = 946684800; - give(json!(epoch), "todate|fromdate", json!(epoch)); - let epoch_ns = 946684800.123456; - give(json!(epoch_ns), "todate|fromdate", json!(epoch_ns)); +yields!(tofromdate, "946684800|todate|fromdate", 946684800); +yields!( + tofromdate_mu, + "946684800.123456|todate|fromdate", + 946684800.123456 +); +#[test] +fn fromtodate() { let iso = "2000-01-01T00:00:00Z"; give(json!(iso), "fromdate|todate", json!(iso)); - let iso_ns = "2000-01-01T00:00:00.123456000Z"; - give(json!(iso_ns), "fromdate|todate", json!(iso_ns)); + let iso_mu = "2000-01-01T00:00:00.123456Z"; + give(json!(iso_mu), "fromdate|todate", json!(iso_mu)); } yields!(