Repository: MiSawa/xq (rust)

Issue:
strftime / strflocaltime / todateiso8601 / todate after strptime does not work
jq supports using strftime/strflocaltime/todateiso8601/todate  against the output of strptime.
```sh
❯ jq 'strptime("%F %T") | strftime("%Y %m %d %H %M %S")' <<< '"2022-03-01 12:10:20"'
"2022 03 01 12 10 20"

❯ jq 'strptime("%F %T") | todateiso8601' <<< '"2022-03-01 12:10:20"'
"2022-03-01T12:10:20Z"

❯ xq 'strptime("%F %T") | strftime("%Y %m %d %H %M %S")' <<< '"2022-03-01 12:10:20"'
Error: InvalidArgType("strftime", [2022, 2, 1, 12, 10, 20, 2, 59])

❯ xq 'strptime("%F %T") | todateiso8601' <<< '"2022-03-01 12:10:20"'
Error: InvalidArgType("strftime", [2022, 2, 1, 12, 10, 20, 2, 59])

❯ xq --version
xq 0.2.10-ad33a92d252fc4842f10730973e16f5da8434035
```

Pull request description:
Refactor implementation of date and time functions
This PR refactors date/time functions. This is necessary step to use the logic of `mktime` for `strftime` and `strptime`, to fix #97.

Patch:
diff --git a/src/intrinsic/time.rs b/src/intrinsic/time.rs
index 7d450b8..74cd529 100644
--- a/src/intrinsic/time.rs
+++ b/src/intrinsic/time.rs
@@ -7,34 +7,39 @@ use crate::{
 use chrono::{DateTime, Datelike, Local, TimeZone, Timelike, Utc};
 use std::rc::Rc;
 
-fn try_unwrap_number(func: &'static str, value: &Value) -> Result<PrimitiveReal> {
+fn try_unwrap_number(value: &Value) -> Option<PrimitiveReal> {
     match value {
-        Value::Number(n) => Ok(n.to_primitive_real()),
-        _ => Err(QueryExecutionError::InvalidArgType(func, value.clone())),
+        Value::Number(n) => Some(n.to_primitive_real()),
+        _ => None,
     }
 }
-fn try_unwrap_string(func: &'static str, value: &Value) -> Result<RcString> {
+
+fn try_unwrap_string(value: &Value) -> Option<RcString> {
     match value {
-        Value::String(s) => Ok(s.clone()),
-        _ => Err(QueryExecutionError::InvalidArgType(func, value.clone())),
+        Value::String(s) => Some(s.clone()),
+        _ => None,
     }
 }
-fn try_unwrap_array(func: &'static str, value: &Value) -> Result<Rc<Array>> {
+
+fn try_unwrap_array(value: &Value) -> Option<Rc<Array>> {
     match value {
-        Value::Array(arr) => Ok(arr.clone()),
-        _ => Err(QueryExecutionError::InvalidArgType(func, value.clone())),
+        Value::Array(arr) => Some(arr.clone()),
+        _ => None,
     }
 }
+
 fn timestamp_to_time<TZ: TimeZone>(tz: &TZ, timestamp: PrimitiveReal) -> DateTime<TZ> {
     let seconds = timestamp.div_euclid(1.0) as i64;
     let nanos = (timestamp.rem_euclid(1.0) * 1e9) as u32;
     tz.timestamp(seconds, nanos)
 }
+
 fn time_to_timestamp<TZ: TimeZone>(dt: &DateTime<TZ>) -> Value {
     let nanos = dt.timestamp_nanos();
     Value::number((nanos as PrimitiveReal) / 1e9)
 }
-fn break_down_time<DT: Datelike + Timelike>(dt: &DT) -> Value {
+
+fn time_to_array<DT: Datelike + Timelike>(dt: &DT) -> Value {
     let v = vec![
         Value::number(dt.year()),
         Value::number(dt.month0()),
@@ -48,91 +53,80 @@ fn break_down_time<DT: Datelike + Timelike>(dt: &DT) -> Value {
     Array::from_vec(v).into()
 }
 
+fn try_array_to_time<TZ: TimeZone>(tz: &TZ, value: &Value) -> Option<DateTime<TZ>> {
+    let arr = try_unwrap_array(value)?;
+    let year = try_unwrap_number(arr.get(0)?)?;
+    let month0 = try_unwrap_number(arr.get(1)?)?;
+    let day = try_unwrap_number(arr.get(2)?)?;
+    let hour = try_unwrap_number(arr.get(3)?)?;
+    let minute = try_unwrap_number(arr.get(4)?)?;
+    let second = try_unwrap_number(arr.get(5)?)?;
+    Some(
+        tz.ymd(year as i32, month0 as u32 + 1, day as u32)
+            .and_hms_nano(
+                hour as u32,
+                minute as u32,
+                second.div_euclid(1.0) as u32,
+                (second.rem_euclid(1.0) * 1e9) as u32,
+            ),
+    )
+}
+
 pub(crate) fn format_time(context: Value, format: Value) -> Result<Value> {
-    let timestamp = try_unwrap_number("strftime", &context)?;
-    let format = try_unwrap_string("strftime", &format)?;
-    let dt = timestamp_to_time(&Utc, timestamp);
+    let dt = try_unwrap_number(&context)
+        .map(|timestamp| timestamp_to_time(&Utc, timestamp))
+        .or_else(|| try_array_to_time(&Utc, &context))
+        .ok_or(QueryExecutionError::InvalidArgType("strftime", context))?;
+    let format = try_unwrap_string(&format)
+        .ok_or(QueryExecutionError::InvalidArgType("strftime", format))?;
     let s = format!("{}", dt.format(format.as_ref()));
     Ok(Value::string(s))
 }
 
 pub(crate) fn format_time_local(context: Value, format: Value) -> Result<Value> {
-    let timestamp = try_unwrap_number("strflocaltime", &context)?;
-    let format = try_unwrap_string("strflocaltime", &format)?;
-    let dt = timestamp_to_time(&Local, timestamp);
+    let dt = try_unwrap_number(&context)
+        .map(|timestamp| timestamp_to_time(&Local, timestamp))
+        .or_else(|| try_array_to_time(&Local, &context))
+        .ok_or(QueryExecutionError::InvalidArgType(
+            "strflocaltime",
+            context,
+        ))?;
+    let format = try_unwrap_string(&format)
+        .ok_or(QueryExecutionError::InvalidArgType("strflocaltime", format))?;
     let s = format!("{}", dt.format(format.as_ref()));
     Ok(Value::string(s))
 }
 
 pub(crate) fn parse_time(context: Value, format: Value) -> Result<Value> {
-    let time = try_unwrap_string("strptime", &context)?;
-    let format = try_unwrap_string("strptime", &format)?;
+    let time = try_unwrap_string(&context)
+        .ok_or(QueryExecutionError::InvalidArgType("strptime", context))?;
+    let format = try_unwrap_string(&format)
+        .ok_or(QueryExecutionError::InvalidArgType("strptime", format))?;
     let dt = chrono::NaiveDateTime::parse_from_str(time.as_ref(), format.as_ref())?;
-    Ok(break_down_time(&dt))
+    Ok(time_to_array(&dt))
 }
 
 pub(crate) fn gm_time(context: Value) -> Result<Value> {
-    let timestamp = try_unwrap_number("gmtime", &context)?;
+    let timestamp = try_unwrap_number(&context)
+        .ok_or(QueryExecutionError::InvalidArgType("gmtime", context))?;
     let dt = timestamp_to_time(&Utc, timestamp);
-    Ok(break_down_time(&dt))
+    Ok(time_to_array(&dt))
 }
 
 pub(crate) fn gm_time_local(context: Value) -> Result<Value> {
-    let timestamp = try_unwrap_number("localtime", &context)?;
+    let timestamp = try_unwrap_number(&context)
+        .ok_or(QueryExecutionError::InvalidArgType("localtime", context))?;
     let dt = timestamp_to_time(&Local, timestamp);
-    Ok(break_down_time(&dt))
+    Ok(time_to_array(&dt))
 }
 
 pub(crate) fn mk_time(context: Value) -> Result<Value> {
-    let broken = try_unwrap_array("mktime", &context)?;
-    let year = try_unwrap_number(
-        "mktime",
-        broken
-            .get(0)
-            .ok_or_else(|| QueryExecutionError::InvalidIndex(Value::number(0)))?,
-    )?;
-    let month0 = try_unwrap_number(
-        "mktime",
-        broken
-            .get(1)
-            .ok_or_else(|| QueryExecutionError::InvalidIndex(Value::number(1)))?,
-    )?;
-    let day = try_unwrap_number(
-        "mktime",
-        broken
-            .get(2)
-            .ok_or_else(|| QueryExecutionError::InvalidIndex(Value::number(2)))?,
-    )?;
-    let hour = try_unwrap_number(
-        "mktime",
-        broken
-            .get(3)
-            .ok_or_else(|| QueryExecutionError::InvalidIndex(Value::number(3)))?,
-    )?;
-    let minute = try_unwrap_number(
-        "mktime",
-        broken
-            .get(4)
-            .ok_or_else(|| QueryExecutionError::InvalidIndex(Value::number(4)))?,
-    )?;
-    let second = try_unwrap_number(
-        "mktime",
-        broken
-            .get(5)
-            .ok_or_else(|| QueryExecutionError::InvalidIndex(Value::number(5)))?,
-    )?;
-    let dt = Utc
-        .ymd(year as i32, month0 as u32 + 1, day as u32)
-        .and_hms_nano(
-            hour as u32,
-            minute as u32,
-            second.div_euclid(1.0) as u32,
-            (second.rem_euclid(1.0) * 1e9) as u32,
-        );
+    let dt = try_array_to_time(&Utc, &context)
+        .ok_or(QueryExecutionError::InvalidArgType("mktime", context))?;
     Ok(time_to_timestamp(&dt))
 }
 
-pub(crate) fn now(_context: Value) -> Result<Value> {
+pub(crate) fn now(_: Value) -> Result<Value> {
     let now = Utc::now();
     Ok(time_to_timestamp(&now))
 }


Test patch:
diff --git a/tests/hand_written/mod.rs b/tests/hand_written/mod.rs
index df7dbbb..8779775 100644
--- a/tests/hand_written/mod.rs
+++ b/tests/hand_written/mod.rs
@@ -635,3 +635,16 @@ test!(
     "WzEsMiwzXQ=="
     "#
 );
+
+test!(
+    strptime_strftime,
+    r#"
+    strptime("%FT%TZ") | strftime("%c")
+    "#,
+    r#"
+    "2015-03-05T23:51:47Z"
+    "#,
+    r#"
+    "Thu Mar  5 23:51:47 2015"
+    "#
+);