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