1 use std::convert::TryInto;
2 
3 use chrono::FixedOffset;
4 use liquid_core::Expression;
5 use liquid_core::Result;
6 use liquid_core::Runtime;
7 use liquid_core::{
8     Display_filter, Filter, FilterParameters, FilterReflection, FromFilterParameters, ParseFilter,
9 };
10 use liquid_core::{Value, ValueView};
11 
12 use crate::invalid_input;
13 
14 // liquid-rust proprietary
15 
16 #[derive(Debug, FilterParameters)]
17 struct DateInTzArgs {
18     #[parameter(description = "The format to return the date in.", arg_type = "str")]
19     format: Expression,
20     #[parameter(
21         description = "The timezone to convert the date to.",
22         arg_type = "integer"
23     )]
24     timezone: Expression,
25 }
26 
27 #[derive(Clone, ParseFilter, FilterReflection)]
28 #[filter(
29     name = "date_in_tz",
30     description = "Converts a timestamp into another date format and timezone.",
31     parameters(DateInTzArgs),
32     parsed(DateInTzFilter)
33 )]
34 pub struct DateInTz;
35 
36 #[derive(Debug, FromFilterParameters, Display_filter)]
37 #[name = "date_in_tz"]
38 struct DateInTzFilter {
39     #[parameters]
40     args: DateInTzArgs,
41 }
42 
43 impl Filter for DateInTzFilter {
evaluate(&self, input: &dyn ValueView, runtime: &dyn Runtime) -> Result<Value>44     fn evaluate(&self, input: &dyn ValueView, runtime: &dyn Runtime) -> Result<Value> {
45         let args = self.args.evaluate(runtime)?;
46 
47         let date = input
48             .as_scalar()
49             .and_then(|s| s.to_date_time())
50             .ok_or_else(|| invalid_input("Invalid date format"))?;
51 
52         let timezone = FixedOffset::east(
53             (args.timezone * 3600)
54                 .try_into()
55                 .map_err(|_| invalid_input("Timezone was too large"))?,
56         );
57 
58         let formatter = date.with_timezone(&timezone).format(args.format.as_str());
59         let date = formatter.to_string();
60         Ok(Value::scalar(date))
61     }
62 }
63 
64 #[cfg(test)]
65 mod tests {
66     use super::*;
67 
68     #[test]
unit_date_in_tz_same_day()69     fn unit_date_in_tz_same_day() {
70         let unit_result = liquid_core::call_filter!(
71             DateInTz,
72             "13 Jun 2016 12:00:00 +0000",
73             "%Y-%m-%d %H:%M:%S %z",
74             3i64
75         )
76         .unwrap();
77         let desired_result = liquid_core::value!("2016-06-13 15:00:00 +0300");
78         assert_eq!(unit_result, desired_result);
79     }
80 
81     #[test]
unit_date_in_tz_previous_day()82     fn unit_date_in_tz_previous_day() {
83         let unit_result = liquid_core::call_filter!(
84             DateInTz,
85             "13 Jun 2016 12:00:00 +0000",
86             "%Y-%m-%d %H:%M:%S %z",
87             -13i64
88         )
89         .unwrap();
90         let desired_result = liquid_core::value!("2016-06-12 23:00:00 -1300");
91         assert_eq!(unit_result, desired_result);
92     }
93 
94     #[test]
unit_date_in_tz_next_day()95     fn unit_date_in_tz_next_day() {
96         let unit_result = liquid_core::call_filter!(
97             DateInTz,
98             "13 Jun 2016 12:00:00 +0000",
99             "%Y-%m-%d %H:%M:%S %z",
100             13i64
101         )
102         .unwrap();
103         let desired_result = liquid_core::value!("2016-06-14 01:00:00 +1300");
104         assert_eq!(unit_result, desired_result);
105     }
106 
107     #[test]
unit_date_in_tz_input_not_a_string()108     fn unit_date_in_tz_input_not_a_string() {
109         liquid_core::call_filter!(DateInTz, 0f64, "%Y-%m-%d %H:%M:%S %z", 0i64).unwrap_err();
110     }
111 
112     #[test]
unit_date_in_tz_input_not_a_date_string()113     fn unit_date_in_tz_input_not_a_date_string() {
114         liquid_core::call_filter!(DateInTz, "blah blah blah", "%Y-%m-%d %H:%M:%S %z", 0i64)
115             .unwrap_err();
116     }
117 
118     #[test]
unit_date_in_tz_offset_not_a_num()119     fn unit_date_in_tz_offset_not_a_num() {
120         liquid_core::call_filter!(
121             DateInTz,
122             "13 Jun 2016 12:00:00 +0000",
123             "%Y-%m-%d %H:%M:%S %z",
124             "Hello"
125         )
126         .unwrap_err();
127     }
128 
129     #[test]
unit_date_in_tz_zero_arguments()130     fn unit_date_in_tz_zero_arguments() {
131         liquid_core::call_filter!(DateInTz, "13 Jun 2016 12:00:00 +0000").unwrap_err();
132     }
133 
134     #[test]
unit_date_in_tz_one_argument()135     fn unit_date_in_tz_one_argument() {
136         liquid_core::call_filter!(
137             DateInTz,
138             "13 Jun 2016 12:00:00 +0000",
139             "%Y-%m-%d %H:%M:%S %z"
140         )
141         .unwrap_err();
142     }
143 
144     #[test]
unit_date_in_tz_three_arguments()145     fn unit_date_in_tz_three_arguments() {
146         liquid_core::call_filter!(
147             DateInTz,
148             "13 Jun 2016 12:00:00 +0000",
149             "%Y-%m-%d %H:%M:%S %z",
150             0f64,
151             1f64
152         )
153         .unwrap_err();
154     }
155 }
156