Repository: cobalt-org/liquid-rust (rust)

Issue:
Tests fail because of a date without time is used, do we need to support that?
Example test:
```rust
    assert_eq!(v!("2001-02-03"), filters!(escape, date(2001, 2, 3)));
```

Pull request:
Add support for a date only type
- [ ] Tests created for any new feature or regression tests for bugfixes.

Fixes #253 


Patch:
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
index 747f1e3..4ef4acd 100644
--- a/azure-pipelines.yml
+++ b/azure-pipelines.yml
@@ -8,7 +8,7 @@ schedules:
 stages:
 - template: azure/stages.yml@templates
   parameters:
-    minrust: 1.34.0
+    minrust: 1.37.0
     # Crashes for some reason. Note: does not run in PRs, so needs to be tested
     # in `master`.
     #codecov_token: $(CODECOV_TOKEN_SECRET)
diff --git a/liquid-derive/src/filter_parameters.rs b/liquid-derive/src/filter_parameters.rs
index d5bd6f0..ff8eaea 100644
--- a/liquid-derive/src/filter_parameters.rs
+++ b/liquid-derive/src/filter_parameters.rs
@@ -332,6 +332,7 @@ enum FilterParameterType {
     Integer,
     Float,
     Bool,
+    DateTime,
     Date,
     Str,
 }
@@ -344,9 +345,10 @@ impl FromStr for FilterParameterType {
             "integer" => Ok(FilterParameterType::Integer),
             "float" => Ok(FilterParameterType::Float),
             "bool" => Ok(FilterParameterType::Bool),
+            "date_time" => Ok(FilterParameterType::DateTime),
             "date" => Ok(FilterParameterType::Date),
             "str" => Ok(FilterParameterType::Str),
-            _ => Err(format!("Expected one of the following: \"any\", \"integer\", \"float\", \"bool\", \"date\" or \"str\". Found \"{}\".", s)),
+            _ => Err(format!("Expected one of the following: \"any\", \"integer\", \"float\", \"bool\", \"date_time\", \"date\" or \"str\". Found \"{}\".", s)),
         }
     }
 }
@@ -502,6 +504,15 @@ fn generate_evaluate_field(field: &FilterParameter) -> TokenStream {
                     .context("cause", "Boolean expected")
             )?
         },
+        FilterParameterType::DateTime => quote! {
+            .as_scalar()
+            .and_then(::liquid::value::Scalar::to_date_time)
+            .ok_or_else(||
+                ::liquid::error::Error::with_msg("Invalid argument")
+                    .context("argument", #liquid_name)
+                    .context("cause", "DateTime expected")
+            )?
+        },
         FilterParameterType::Date => quote! {
             .as_scalar()
             .and_then(::liquid::value::Scalar::to_date)
@@ -658,6 +669,7 @@ fn generate_evaluated_struct(filter_parameters: &FilterParameters) -> TokenStrea
             FilterParameterType::Integer => quote! { i32 },
             FilterParameterType::Float => quote! { f64 },
             FilterParameterType::Bool => quote! { bool },
+            FilterParameterType::DateTime => quote! { ::liquid::value::DateTime },
             FilterParameterType::Date => quote! { ::liquid::value::Date },
             FilterParameterType::Str => quote! { ::std::borrow::Cow<'a, str> },
         };
diff --git a/liquid-value/Cargo.toml b/liquid-value/Cargo.toml
index b93bd2e..7435171 100644
--- a/liquid-value/Cargo.toml
+++ b/liquid-value/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "liquid-value"
-version = "0.19.1"
+version = "0.19.0"
 authors = ["Johann Hofmann <mail@johann-hofmann.com>"]
 description = "The liquid templating language for Rust"
 repository = "https://github.com/cobalt-org/liquid-rust/tree/master/liquid-value"
diff --git a/liquid-value/src/scalar.rs b/liquid-value/src/scalar.rs
index ab5063f..3da6b4a 100644
--- a/liquid-value/src/scalar.rs
+++ b/liquid-value/src/scalar.rs
@@ -4,8 +4,11 @@ use std::fmt;
 
 use chrono;
 
-/// Liquid's native date/time type.
-pub type Date = chrono::DateTime<chrono::FixedOffset>;
+/// Liquid's native date + time type.
+pub type DateTime = chrono::DateTime<chrono::FixedOffset>;
+
+/// Liquid's native date only type.
+pub type Date = chrono::NaiveDate;
 
 /// A Liquid scalar value
 #[derive(Clone, Debug, Serialize, Deserialize)]
@@ -21,6 +24,8 @@ enum ScalarCowEnum<'s> {
     Integer(i32),
     Float(f64),
     Bool(bool),
+    #[serde(with = "friendly_date_time")]
+    DateTime(DateTime),
     #[serde(with = "friendly_date")]
     Date(Date),
     Str(borrow::Cow<'s, str>),
@@ -56,6 +61,7 @@ impl<'s> ScalarCow<'s> {
             ScalarCowEnum::Integer(x) => ScalarCow::new(x),
             ScalarCowEnum::Float(x) => ScalarCow::new(x),
             ScalarCowEnum::Bool(x) => ScalarCow::new(x),
+            ScalarCowEnum::DateTime(x) => ScalarCow::new(x),
             ScalarCowEnum::Date(x) => ScalarCow::new(x),
             ScalarCowEnum::Str(ref x) => ScalarCow::new(x.as_ref()),
         }
@@ -67,6 +73,9 @@ impl<'s> ScalarCow<'s> {
             ScalarCowEnum::Integer(ref x) => borrow::Cow::Owned(x.to_string()),
             ScalarCowEnum::Float(ref x) => borrow::Cow::Owned(x.to_string()),
             ScalarCowEnum::Bool(ref x) => borrow::Cow::Owned(x.to_string()),
+            ScalarCowEnum::DateTime(ref x) => {
+                borrow::Cow::Owned(x.format(DATE_TIME_FORMAT).to_string())
+            }
             ScalarCowEnum::Date(ref x) => borrow::Cow::Owned(x.format(DATE_FORMAT).to_string()),
             ScalarCowEnum::Str(ref x) => borrow::Cow::Borrowed(x.as_ref()),
         }
@@ -78,7 +87,8 @@ impl<'s> ScalarCow<'s> {
             ScalarCowEnum::Integer(x) => x.to_string(),
             ScalarCowEnum::Float(x) => x.to_string(),
             ScalarCowEnum::Bool(x) => x.to_string(),
-            ScalarCowEnum::Date(x) => x.to_string(),
+            ScalarCowEnum::DateTime(x) => x.to_string(),
+            ScalarCowEnum::Date(x) => x.format(DATE_FORMAT).to_string(),
             ScalarCowEnum::Str(x) => x.into_owned(),
         }
     }
@@ -110,9 +120,19 @@ impl<'s> ScalarCow<'s> {
         }
     }
 
-    /// Interpret as a date, if possible
+    /// Interpret as a date time, if possible
+    pub fn to_date_time(&self) -> Option<DateTime> {
+        match self.0 {
+            ScalarCowEnum::DateTime(ref x) => Some(*x),
+            ScalarCowEnum::Str(ref x) => parse_date_time(x.as_ref()),
+            _ => None,
+        }
+    }
+
+    /// Interpret as a date time, if possible
     pub fn to_date(&self) -> Option<Date> {
         match self.0 {
+            ScalarCowEnum::DateTime(ref x) => Some(x.naive_utc().date()),
             ScalarCowEnum::Date(ref x) => Some(*x),
             ScalarCowEnum::Str(ref x) => parse_date(x.as_ref()),
             _ => None,
@@ -144,6 +164,7 @@ impl<'s> ScalarCow<'s> {
             ScalarCowEnum::Integer(_) => "whole number",
             ScalarCowEnum::Float(_) => "fractional number",
             ScalarCowEnum::Bool(_) => "boolean",
+            ScalarCowEnum::DateTime(_) => "date time",
             ScalarCowEnum::Date(_) => "date",
             ScalarCowEnum::Str(_) => "string",
         }
@@ -174,6 +195,14 @@ impl<'s> From<bool> for ScalarCow<'s> {
     }
 }
 
+impl<'s> From<DateTime> for ScalarCow<'s> {
+    fn from(s: DateTime) -> Self {
+        ScalarCow {
+            0: ScalarCowEnum::DateTime(s),
+        }
+    }
+}
+
 impl<'s> From<Date> for ScalarCow<'s> {
     fn from(s: Date) -> Self {
         ScalarCow {
@@ -241,6 +270,13 @@ impl<'s> PartialEq<bool> for ScalarCow<'s> {
     }
 }
 
+impl<'s> PartialEq<DateTime> for ScalarCow<'s> {
+    fn eq(&self, other: &DateTime) -> bool {
+        let other = (*other).into();
+        scalar_eq(self, &other)
+    }
+}
+
 impl<'s> PartialEq<Date> for ScalarCow<'s> {
     fn eq(&self, other: &Date) -> bool {
         let other = (*other).into();
@@ -284,6 +320,13 @@ impl<'s> PartialOrd<bool> for ScalarCow<'s> {
     }
 }
 
+impl<'s> PartialOrd<DateTime> for ScalarCow<'s> {
+    fn partial_cmp(&self, other: &DateTime) -> Option<Ordering> {
+        let other = (*other).into();
+        scalar_cmp(self, &other)
+    }
+}
+
 impl<'s> PartialOrd<Date> for ScalarCow<'s> {
     fn partial_cmp(&self, other: &Date) -> Option<Ordering> {
         let other = (*other).into();
@@ -308,6 +351,7 @@ impl<'s> fmt::Display for ScalarSource<'s> {
             ScalarCowEnum::Integer(ref x) => write!(f, "{}", x),
             ScalarCowEnum::Float(ref x) => write!(f, "{}", x),
             ScalarCowEnum::Bool(ref x) => write!(f, "{}", x),
+            ScalarCowEnum::DateTime(ref x) => write!(f, "{}", x.format(DATE_TIME_FORMAT)),
             ScalarCowEnum::Date(ref x) => write!(f, "{}", x.format(DATE_FORMAT)),
             ScalarCowEnum::Str(ref x) => write!(f, r#""{}""#, x),
         }
@@ -325,12 +369,21 @@ impl<'s> fmt::Display for ScalarRendered<'s> {
             ScalarCowEnum::Integer(ref x) => write!(f, "{}", x),
             ScalarCowEnum::Float(ref x) => write!(f, "{}", x),
             ScalarCowEnum::Bool(ref x) => write!(f, "{}", x),
+            ScalarCowEnum::DateTime(ref x) => write!(f, "{}", x.format(DATE_TIME_FORMAT)),
             ScalarCowEnum::Date(ref x) => write!(f, "{}", x.format(DATE_FORMAT)),
             ScalarCowEnum::Str(ref x) => write!(f, "{}", x),
         }
     }
 }
 
+fn date_to_datetime(date: Date, offset: chrono::FixedOffset) -> DateTime {
+    use chrono::{NaiveDateTime, NaiveTime};
+    DateTime::from_utc(
+        NaiveDateTime::new(date, NaiveTime::from_hms(0, 0, 0)),
+        offset,
+    )
+}
+
 fn scalar_eq<'s>(lhs: &ScalarCow<'s>, rhs: &ScalarCow<'s>) -> bool {
     match (&lhs.0, &rhs.0) {
         (&ScalarCowEnum::Integer(x), &ScalarCowEnum::Integer(y)) => x == y,
@@ -338,7 +391,14 @@ fn scalar_eq<'s>(lhs: &ScalarCow<'s>, rhs: &ScalarCow<'s>) -> bool {
         (&ScalarCowEnum::Float(x), &ScalarCowEnum::Integer(y)) => x == (f64::from(y)),
         (&ScalarCowEnum::Float(x), &ScalarCowEnum::Float(y)) => x == y,
         (&ScalarCowEnum::Bool(x), &ScalarCowEnum::Bool(y)) => x == y,
+        (&ScalarCowEnum::DateTime(x), &ScalarCowEnum::DateTime(y)) => x == y,
         (&ScalarCowEnum::Date(x), &ScalarCowEnum::Date(y)) => x == y,
+        (&ScalarCowEnum::DateTime(x), &ScalarCowEnum::Date(y)) => {
+            x == date_to_datetime(y, *x.offset())
+        }
+        (&ScalarCowEnum::Date(x), &ScalarCowEnum::DateTime(y)) => {
+            date_to_datetime(x, *y.offset()) == y
+        }
         (&ScalarCowEnum::Str(ref x), &ScalarCowEnum::Str(ref y)) => x == y,
         // encode Ruby truthiness: all values except false and nil are true
         (_, &ScalarCowEnum::Bool(b)) | (&ScalarCowEnum::Bool(b), _) => b,
@@ -353,13 +413,43 @@ fn scalar_cmp<'s>(lhs: &ScalarCow<'s>, rhs: &ScalarCow<'s>) -> Option<Ordering>
         (&ScalarCowEnum::Float(x), &ScalarCowEnum::Integer(y)) => x.partial_cmp(&(f64::from(y))),
         (&ScalarCowEnum::Float(x), &ScalarCowEnum::Float(y)) => x.partial_cmp(&y),
         (&ScalarCowEnum::Bool(x), &ScalarCowEnum::Bool(y)) => x.partial_cmp(&y),
+        (&ScalarCowEnum::DateTime(x), &ScalarCowEnum::DateTime(y)) => x.partial_cmp(&y),
         (&ScalarCowEnum::Date(x), &ScalarCowEnum::Date(y)) => x.partial_cmp(&y),
+        (&ScalarCowEnum::DateTime(x), &ScalarCowEnum::Date(y)) => {
+            x.partial_cmp(&date_to_datetime(y, *x.offset()))
+        }
+        (&ScalarCowEnum::Date(x), &ScalarCowEnum::DateTime(y)) => {
+            date_to_datetime(x, *y.offset()).partial_cmp(&y)
+        }
         (&ScalarCowEnum::Str(ref x), &ScalarCowEnum::Str(ref y)) => x.partial_cmp(y),
         _ => None,
     }
 }
 
-const DATE_FORMAT: &str = "%Y-%m-%d %H:%M:%S %z";
+const DATE_TIME_FORMAT: &str = "%Y-%m-%d %H:%M:%S %z";
+
+mod friendly_date_time {
+    use super::*;
+    use serde::{self, Deserialize, Deserializer, Serializer};
+
+    pub(crate) fn serialize<S>(date: &DateTime, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: Serializer,
+    {
+        let s = date.format(DATE_TIME_FORMAT).to_string();
+        serializer.serialize_str(&s)
+    }
+
+    pub(crate) fn deserialize<'de, D>(deserializer: D) -> Result<DateTime, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        let s = String::deserialize(deserializer)?;
+        DateTime::parse_from_str(&s, DATE_TIME_FORMAT).map_err(serde::de::Error::custom)
+    }
+}
+
+const DATE_FORMAT: &str = "%Y-%m-%d";
 
 mod friendly_date {
     use super::*;
@@ -378,20 +468,37 @@ mod friendly_date {
         D: Deserializer<'de>,
     {
         let s = String::deserialize(deserializer)?;
-        Date::parse_from_str(&s, DATE_FORMAT).map_err(serde::de::Error::custom)
+        chrono::NaiveDate::parse_from_str(&s, DATE_FORMAT).map_err(serde::de::Error::custom)
     }
 }
 
-fn parse_date(s: &str) -> Option<Date> {
+fn parse_date_time(s: &str) -> Option<DateTime> {
     match s {
-        "now" | "today" => {
-            let now = chrono::offset::Utc::now();
+        "now" => {
+            use chrono::offset;
+            let now = offset::Utc::now();
             let now = now.naive_utc();
-            let now = chrono::DateTime::from_utc(now, chrono::offset::FixedOffset::east(0));
+            let now = DateTime::from_utc(now, offset::FixedOffset::east(0));
             Some(now)
         }
         _ => {
             let formats = ["%d %B %Y %H:%M:%S %z", "%Y-%m-%d %H:%M:%S %z"];
+            formats
+                .iter()
+                .filter_map(|f| DateTime::parse_from_str(s, f).ok())
+                .next()
+        }
+    }
+}
+
+fn parse_date(s: &str) -> Option<Date> {
+    match s {
+        "today" => {
+            use chrono::offset::Utc;
+            Some(Utc::today().naive_utc())
+        }
+        _ => {
+            let formats = ["%d %B %Y", "%Y-%m-%d"];
             formats
                 .iter()
                 .filter_map(|f| Date::parse_from_str(s, f).ok())
@@ -615,18 +722,20 @@ mod test {
     }
 
     #[test]
-    fn parse_date_empty_is_bad() {
+    fn parse_date_time_empty_is_bad() {
+        assert!(parse_date_time("").is_none());
         assert!(parse_date("").is_none());
     }
 
     #[test]
-    fn parse_date_bad() {
+    fn parse_date_time_bad() {
+        assert!(parse_date_time("aaaaa").is_none());
         assert!(parse_date("aaaaa").is_none());
     }
 
     #[test]
-    fn parse_date_now() {
-        assert!(parse_date("now").is_some());
+    fn parse_date_time_now() {
+        assert!(parse_date_time("now").is_some());
     }
 
     #[test]
diff --git a/release.toml b/release.toml
index 5b096a3..c2b7cf6 100644
--- a/release.toml
+++ b/release.toml
@@ -1,4 +1,4 @@
 pre-release-commit-message = "chore({{crate_name}}): Release {{version}}"
 no-dev-version = true
-tag-message = "{{tag_name}}"
+tag-message = "{{prefix}}v{{version}}"
 tag-name = "{{prefix}}v{{version}}"
diff --git a/src/filters/extra/date.rs b/src/filters/extra/date.rs
index 453ca32..e4609ab 100644
--- a/src/filters/extra/date.rs
+++ b/src/filters/extra/date.rs
@@ -42,7 +42,7 @@ impl Filter for DateInTzFilter {
 
         let date = input
             .as_scalar()
-            .and_then(Scalar::to_date)
+            .and_then(Scalar::to_date_time)
             .ok_or_else(|| invalid_input("Invalid date format"))?;
 
         let timezone = FixedOffset::east(args.timezone * 3600);
diff --git a/src/filters/std/date.rs b/src/filters/std/date.rs
index de9677f..ff06531 100644
--- a/src/filters/std/date.rs
+++ b/src/filters/std/date.rs
@@ -31,7 +31,7 @@ impl Filter for DateFilter {
     fn evaluate(&self, input: &Value, context: &Context) -> Result<Value> {
         let args = self.args.evaluate(context)?;
 
-        let date = input.as_scalar().and_then(Scalar::to_date);
+        let date = input.as_scalar().and_then(Scalar::to_date_time);
         match date {
             Some(date) if !args.format.is_empty() => {
                 Ok(Value::scalar(date.format(args.format.as_ref()).to_string()))
diff --git a/src/filters/std/html.rs b/src/filters/std/html.rs
index e9819fa..bdfe5d7 100644
--- a/src/filters/std/html.rs
+++ b/src/filters/std/html.rs
@@ -19,6 +19,9 @@ fn nr_escaped(text: &str) -> usize {
 // https://github.com/rust-lang/rust/blob/master/src/librustdoc/html/escape.rs
 // Retrieved 2016-11-19.
 fn escape(input: &Value, once_p: bool) -> Result<Value> {
+    if input.is_nil() {
+        return Ok(Value::nil());
+    }
     let s = input.to_str();
     let mut result = String::new();
     let mut last = 0;


Test patch:
diff --git a/tests/conformance_ruby/standard_filter_test.rs b/tests/conformance_ruby/standard_filter_test.rs
index 58b4e3d..b1b7f24 100644
--- a/tests/conformance_ruby/standard_filter_test.rs
+++ b/tests/conformance_ruby/standard_filter_test.rs
@@ -91,7 +91,6 @@ fn test_split() {
 }
 
 #[test]
-#[should_panic] // liquid-rust#253
 fn test_escape() {
     assert_eq!(v!("&lt;strong&gt;"), filters!(Escape, v!("<strong>")));
     assert_eq!(v!("1"), filters!(Escape, v!(1)));
@@ -120,7 +119,6 @@ fn test_escape_once() {
 }
 
 #[test]
-#[should_panic] // liquid-rust#253
 fn test_url_encode() {
     assert_eq!(
         v!("foo%2B1%40example.com"),
@@ -132,7 +130,6 @@ fn test_url_encode() {
 }
 
 #[test]
-#[should_panic] // liquid-rust#268
 fn test_url_decode() {
     assert_eq!(v!("foo bar"), filters!(UrlDecode, v!("foo+bar")));
     assert_eq!(v!("foo bar"), filters!(UrlDecode, v!("foo%20bar")));
diff --git a/tests/derive_macros.rs b/tests/derive_macros.rs
index e229372..6043cb4 100644
--- a/tests/derive_macros.rs
+++ b/tests/derive_macros.rs
@@ -147,15 +147,15 @@ pub fn test_derive_mixed_filter_ok() {
 
     let template = parser
         .parse(concat!(
-            "{{ 0 | mix: a: 5, false, c: 4.3, \"2019-02-08 15:34:25 -0800\", \"str\", type: 0 }}\n",
-            "{{ 0 | mix: false, \"2019-02-08 15:34:25 -0800\", type: 0 }}\n",
-            "{{ 0 | mix: false, \"2019-02-08 15:34:25 -0800\", c: 4.3, a: 5, type: 0, \"str\" }}"
+            "{{ 0 | mix: a: 5, false, c: 4.3, \"2019-02-08 15:34:25 -0800\", \"2019-02-08\", \"str\", type: 0 }}\n",
+            "{{ 0 | mix: false, \"2019-02-08 15:34:25 -0800\", \"2019-02-08\", type: 0 }}\n",
+            "{{ 0 | mix: false, \"2019-02-08 15:34:25 -0800\", \"2019-02-08\", c: 4.3, a: 5, type: 0, \"str\" }}"
         ))
         .unwrap();
     let expected = concat!(
-        "<a: 5; b: false; c: 4.3, d: 2019-02-08 15:34:25 -08:00, e: str, type: 0>\n",
-        "<a: None; b: false; c: None, d: 2019-02-08 15:34:25 -08:00, e: None, type: 0>\n",
-        "<a: 5; b: false; c: 4.3, d: 2019-02-08 15:34:25 -08:00, e: str, type: 0>"
+        "<a: 5; b: false; c: 4.3, d: 2019-02-08 15:34:25 -08:00, e: 2019-02-08, f: str, type: 0>\n",
+        "<a: None; b: false; c: None, d: 2019-02-08 15:34:25 -08:00, e: 2019-02-08, f: None, type: 0>\n",
+        "<a: 5; b: false; c: 4.3, d: 2019-02-08 15:34:25 -08:00, e: 2019-02-08, f: str, type: 0>"
     );
 
     let globals = liquid::value::Object::new();
@@ -185,6 +185,8 @@ pub fn test_derive_mixed_filter_reflection() {
     assert_eq!(filter.description(), "Mix it all together.");
     let pos_args = filter.positional_parameters();
 
+    assert_eq!(pos_args.len(), 4);
+
     assert_eq!(pos_args[0].name, "b");
     assert_eq!(pos_args[0].description, "2");
     assert_eq!(pos_args[0].is_optional, false);
@@ -195,10 +197,16 @@ pub fn test_derive_mixed_filter_reflection() {
 
     assert_eq!(pos_args[2].name, "e");
     assert_eq!(pos_args[2].description, "5");
-    assert_eq!(pos_args[2].is_optional, true);
+    assert_eq!(pos_args[2].is_optional, false);
+
+    assert_eq!(pos_args[3].name, "f");
+    assert_eq!(pos_args[3].description, "6");
+    assert_eq!(pos_args[3].is_optional, true);
 
     let kw_args = filter.keyword_parameters();
 
+    assert_eq!(kw_args.len(), 3);
+
     assert_eq!(kw_args[0].name, "a");
     assert_eq!(kw_args[0].description, "1");
     assert_eq!(kw_args[0].is_optional, true);
@@ -208,7 +216,7 @@ pub fn test_derive_mixed_filter_reflection() {
     assert_eq!(kw_args[1].is_optional, true);
 
     assert_eq!(kw_args[2].name, "type");
-    assert_eq!(kw_args[2].description, "6");
+    assert_eq!(kw_args[2].description, "7");
     assert_eq!(kw_args[2].is_optional, false);
 }
 
diff --git a/tests/derive_macros_test_filters/mixed.rs b/tests/derive_macros_test_filters/mixed.rs
index 1bc1034..ad9ee2a 100644
--- a/tests/derive_macros_test_filters/mixed.rs
+++ b/tests/derive_macros_test_filters/mixed.rs
@@ -22,14 +22,17 @@ struct TestMixedFilterParameters {
     #[parameter(description = "3", arg_type = "float", mode = "keyword")]
     c: Option<Expression>,
 
-    #[parameter(description = "4", arg_type = "date")]
+    #[parameter(description = "4", arg_type = "date_time")]
     d: Expression,
 
-    #[parameter(description = "5", arg_type = "str")]
-    e: Option<Expression>,
+    #[parameter(description = "5", arg_type = "date")]
+    e: Expression,
 
-    #[parameter(rename = "type", description = "6", arg_type = "any", mode = "keyword")]
-    f: Expression,
+    #[parameter(description = "6", arg_type = "str")]
+    f: Option<Expression>,
+
+    #[parameter(rename = "type", description = "7", arg_type = "any", mode = "keyword")]
+    g: Expression,
 }
 
 #[derive(Clone, ParseFilter, FilterReflection)]
@@ -56,12 +59,13 @@ impl Filter for TestMixedFilter {
         let b = args.b.to_string();
         let c = args.c.map(|i| i.to_string()).unwrap_or("None".to_string());
         let d = args.d.to_string();
-        let e = args.e.map(|i| i.to_string()).unwrap_or("None".to_string());
-        let f = args.f.to_str();
+        let e = args.e.to_string();
+        let f = args.f.map(|i| i.to_string()).unwrap_or("None".to_string());
+        let g = args.g.to_str();
 
         let result = format!(
-            "<a: {}; b: {}; c: {}, d: {}, e: {}, type: {}>",
-            a, b, c, d, e, f
+            "<a: {}; b: {}; c: {}, d: {}, e: {}, f: {}, type: {}>",
+            a, b, c, d, e, f, g
         );
 
         Ok(Value::scalar(result))
diff --git a/tests/test_helper.rs b/tests/test_helper.rs
index 7189984..8471fb3 100644
--- a/tests/test_helper.rs
+++ b/tests/test_helper.rs
@@ -6,10 +6,8 @@ pub use liquid::value::Value::Nil;
 
 #[allow(dead_code)]
 pub fn date(y: i32, m: u32, d: u32) -> liquid::value::Value {
-    use chrono;
-    let base = chrono::naive::NaiveDate::from_ymd(y, m, d).and_hms(0, 0, 0);
-    let date = liquid::value::Date::from_utc(base, chrono::FixedOffset::east(0));
-    liquid::value::Value::scalar(date)
+    use liquid::value::{Date, Value};
+    Value::scalar(Date::from_ymd(y, m, d))
 }
 
 #[allow(dead_code)]