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!(