Skip to content

Commit

Permalink
fix(filters): date_in_tz can't parse cobalt date
Browse files Browse the repository at this point in the history
`date` but not `date_in_tz` was updated to parse more than one date
format when cobalt switched formats.  This updates `date_in_tz` to do so
as well.

This was done by centralizing the date parsing logic as discussed in #48.
  • Loading branch information
epage committed Jan 14, 2018
1 parent 603628d commit 1dae527
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 15 deletions.
22 changes: 7 additions & 15 deletions src/filters/date.rs
@@ -1,4 +1,3 @@
use chrono::DateTime;
#[cfg(feature = "extra-filters")]
use chrono::FixedOffset;

Expand All @@ -16,12 +15,7 @@ pub fn date(input: &Value, args: &[Value]) -> FilterResult {
return Ok(input.clone());
}

let input_string = input.to_str();
let formats = ["%d %B %Y %H:%M:%S %z", "%Y-%m-%d %H:%M:%S %z"];
let date = formats
.iter()
.filter_map(|f| DateTime::parse_from_str(input_string.as_ref(), f).ok())
.next();
let date = input.as_scalar().and_then(Scalar::to_date);
let date = match date {
Some(d) => d,
None => {
Expand All @@ -38,9 +32,10 @@ pub fn date(input: &Value, args: &[Value]) -> FilterResult {
pub fn date_in_tz(input: &Value, args: &[Value]) -> FilterResult {
check_args_len(args, 2, 0)?;

let s = input.to_str();
let date = DateTime::parse_from_str(s.as_ref(), "%d %B %Y %H:%M:%S %z")
.map_err(|e| FilterError::InvalidType(format!("Invalid date format: {}", e)))?;
let date = input
.as_scalar()
.and_then(Scalar::to_date)
.ok_or(FilterError::InvalidType("Invalid date format".into()))?;

let format = args[0].to_str();

Expand Down Expand Up @@ -173,8 +168,7 @@ mod tests {
fn unit_date_in_tz_input_not_a_string() {
let input = &Value::scalar(0f32);
let args = &[tos!("%Y-%m-%d %H:%M:%S %z"), Value::scalar(0i32)];
let desired_result = FilterError::InvalidType("Invalid date format: premature end of input"
.to_owned());
let desired_result = FilterError::InvalidType("Invalid date format".to_owned());
assert_eq!(failed!(date_in_tz, input, args), desired_result);
}

Expand All @@ -183,9 +177,7 @@ mod tests {
fn unit_date_in_tz_input_not_a_date_string() {
let input = &tos!("blah blah blah");
let args = &[tos!("%Y-%m-%d %H:%M:%S %z"), Value::scalar(0i32)];
let desired_result = FilterError::InvalidType("Invalid date format: input contains \
invalid characters"
.to_owned());
let desired_result = FilterError::InvalidType("Invalid date format".to_owned());
assert_eq!(failed!(date_in_tz, input, args), desired_result);
}

Expand Down
63 changes: 63 additions & 0 deletions src/value/scalar.rs
Expand Up @@ -2,6 +2,10 @@ use std::cmp::Ordering;
use std::fmt;
use std::borrow;

use chrono;

pub type Date = chrono::DateTime<chrono::FixedOffset>;

/// A Liquid scalar value
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
Expand All @@ -15,6 +19,8 @@ enum ScalarEnum {
Integer(i32),
Float(f32),
Bool(bool),
#[cfg_attr(feature = "serde", serde(with = "friendly_date"))]
Date(Date),
Str(String),
}

Expand All @@ -28,6 +34,7 @@ impl Scalar {
ScalarEnum::Integer(ref x) => borrow::Cow::Owned(x.to_string()),
ScalarEnum::Float(ref x) => borrow::Cow::Owned(x.to_string()),
ScalarEnum::Bool(ref x) => borrow::Cow::Owned(x.to_string()),
ScalarEnum::Date(ref x) => borrow::Cow::Owned(x.format(DATE_FORMAT).to_string()),
ScalarEnum::Str(ref x) => borrow::Cow::Borrowed(x.as_str()),
}
}
Expand All @@ -37,6 +44,7 @@ impl Scalar {
ScalarEnum::Integer(x) => x.to_string(),
ScalarEnum::Float(x) => x.to_string(),
ScalarEnum::Bool(x) => x.to_string(),
ScalarEnum::Date(x) => x.to_string(),
ScalarEnum::Str(x) => x,
}
}
Expand All @@ -47,6 +55,7 @@ impl Scalar {
ScalarEnum::Integer(ref x) => Some(*x),
ScalarEnum::Float(_) => None,
ScalarEnum::Bool(_) => None,
ScalarEnum::Date(_) => None,
ScalarEnum::Str(ref x) => x.parse::<i32>().ok(),
}
}
Expand All @@ -57,6 +66,7 @@ impl Scalar {
ScalarEnum::Integer(ref x) => Some(*x as f32),
ScalarEnum::Float(ref x) => Some(*x),
ScalarEnum::Bool(_) => None,
ScalarEnum::Date(_) => None,
ScalarEnum::Str(ref x) => x.parse::<f32>().ok(),
}
}
Expand All @@ -67,17 +77,30 @@ impl Scalar {
ScalarEnum::Integer(_) => None,
ScalarEnum::Float(_) => None,
ScalarEnum::Bool(ref x) => Some(*x),
ScalarEnum::Date(_) => None,
ScalarEnum::Str(_) => None,
}
}

/// Interpret as an bool, if possible
pub fn to_date(&self) -> Option<Date> {
match self.0 {
ScalarEnum::Integer(_) => None,
ScalarEnum::Float(_) => None,
ScalarEnum::Bool(_) => None,
ScalarEnum::Date(ref x) => Some(x.clone()),
ScalarEnum::Str(ref x) => parse_date(x.as_str()),
}
}

/// Evaluate using Liquid "truthiness"
pub fn is_truthy(&self) -> bool {
// encode Ruby truthiness: all values except false and nil are true
match self.0 {
ScalarEnum::Integer(_) => true,
ScalarEnum::Float(_) => true,
ScalarEnum::Bool(ref x) => *x,
ScalarEnum::Date(_) => true,
ScalarEnum::Str(_) => true,
}
}
Expand All @@ -89,6 +112,7 @@ impl Scalar {
ScalarEnum::Integer(_) => false,
ScalarEnum::Float(_) => false,
ScalarEnum::Bool(ref x) => !*x,
ScalarEnum::Date(_) => false,
ScalarEnum::Str(ref x) => x.is_empty(),
}
}
Expand All @@ -112,6 +136,12 @@ impl From<bool> for Scalar {
}
}

impl From<Date> for Scalar {
fn from(s: Date) -> Self {
Scalar { 0: ScalarEnum::Date(s) }
}
}

impl From<String> for Scalar {
fn from(s: String) -> Self {
Scalar { 0: ScalarEnum::Str(s) }
Expand All @@ -132,6 +162,7 @@ impl PartialEq<Scalar> for Scalar {
(&ScalarEnum::Float(x), &ScalarEnum::Integer(y)) => x == (y as f32),
(&ScalarEnum::Float(x), &ScalarEnum::Float(y)) => x == y,
(&ScalarEnum::Bool(x), &ScalarEnum::Bool(y)) => x == y,
(&ScalarEnum::Date(x), &ScalarEnum::Date(y)) => x == y,
(&ScalarEnum::Str(ref x), &ScalarEnum::Str(ref y)) => x == y,
// encode Ruby truthiness: all values except false and nil are true
(_, &ScalarEnum::Bool(b)) |
Expand All @@ -151,6 +182,7 @@ impl PartialOrd<Scalar> for Scalar {
(&ScalarEnum::Float(x), &ScalarEnum::Integer(y)) => x.partial_cmp(&(y as f32)),
(&ScalarEnum::Float(x), &ScalarEnum::Float(y)) => x.partial_cmp(&y),
(&ScalarEnum::Bool(x), &ScalarEnum::Bool(y)) => x.partial_cmp(&y),
(&ScalarEnum::Date(x), &ScalarEnum::Date(y)) => x.partial_cmp(&y),
(&ScalarEnum::Str(ref x), &ScalarEnum::Str(ref y)) => x.partial_cmp(y),
_ => None,
}
Expand All @@ -164,6 +196,37 @@ impl fmt::Display for Scalar {
}
}

const DATE_FORMAT: &'static str = "%Y-%m-%d %H:%M:%S %z";

#[cfg(feature = "serde")]
mod friendly_date {
use super::*;
use serde::{self, Deserialize, Serializer, Deserializer};

pub fn serialize<S>(date: &Date, serializer: S) -> Result<S::Ok, S::Error>
where S: Serializer
{
let s = date.format(DATE_FORMAT).to_string();
serializer.serialize_str(&s)
}

pub fn deserialize<'de, D>(deserializer: D) -> Result<Date, D::Error>
where D: Deserializer<'de>
{
let s = String::deserialize(deserializer)?;
Date::parse_from_str(&s, DATE_FORMAT).map_err(serde::de::Error::custom)
}
}

fn parse_date(s: &str) -> Option<Date> {
let formats = ["%d %B %Y %H:%M:%S %z", "%Y-%m-%d %H:%M:%S %z"];
let date = formats
.iter()
.filter_map(|f| Date::parse_from_str(s, f).ok())
.next();
date
}

#[cfg(test)]
mod test {
use super::*;
Expand Down

0 comments on commit 1dae527

Please sign in to comment.