1use crate::compiler::TimeZone;
2use crate::compiler::conversion::Conversion;
3use crate::compiler::prelude::*;
4
5fn parse_timestamp(
6 value: Value,
7 format: &Value,
8 timezone: Option<Value>,
9 ctx: &Context,
10) -> Resolved {
11 match value {
12 Value::Bytes(v) => {
13 let format = format.try_bytes_utf8_lossy()?;
14
15 let timezone_bytes = timezone.map(VrlValueConvert::try_bytes).transpose()?;
16 let timezone = timezone_bytes.as_ref().map(|b| String::from_utf8_lossy(b));
17 let timezone = timezone
18 .as_deref()
19 .map(|timezone| {
20 TimeZone::parse(timezone).ok_or(format!("unable to parse timezone: {timezone}"))
21 })
22 .transpose()?
23 .unwrap_or(*ctx.timezone());
24
25 Conversion::timestamp(&format, timezone)
26 .convert(v)
27 .map_err(|e| e.to_string().into())
28 }
29 Value::Timestamp(_) => Ok(value),
30 _ => Err(format!("unable to convert {} value to timestamp", value.kind_str()).into()),
31 }
32}
33
34#[derive(Clone, Copy, Debug)]
35pub struct ParseTimestamp;
36
37impl Function for ParseTimestamp {
38 fn identifier(&self) -> &'static str {
39 "parse_timestamp"
40 }
41
42 fn usage(&self) -> &'static str {
43 "Parses the `value` in [strptime](https://docs.rs/chrono/latest/chrono/format/strftime/index.html#specifiers) `format`."
44 }
45
46 fn category(&self) -> &'static str {
47 Category::Parse.as_ref()
48 }
49
50 fn internal_failure_reasons(&self) -> &'static [&'static str] {
51 &[
52 "`value` fails to parse using the provided `format`.",
53 "`value` fails to parse using the provided `timezone`.",
54 ]
55 }
56
57 fn return_kind(&self) -> u16 {
58 kind::TIMESTAMP
59 }
60
61 fn examples(&self) -> &'static [Example] {
62 &[
63 example! {
64 title: "Parse timestamp",
65 source: r#"parse_timestamp!("10-Oct-2020 16:00+00:00", format: "%v %R %:z")"#,
66 result: Ok("t'2020-10-10T16:00:00Z'"),
67 },
68 example! {
69 title: "Parse timestamp with timezone",
70 source: r#"parse_timestamp!("16/10/2019 12:00:00", format: "%d/%m/%Y %H:%M:%S", timezone: "Asia/Taipei")"#,
71 result: Ok("t'2019-10-16T04:00:00Z'"),
72 },
73 ]
74 }
75
76 fn compile(
77 &self,
78 _state: &state::TypeState,
79 _ctx: &mut FunctionCompileContext,
80 arguments: ArgumentList,
81 ) -> Compiled {
82 let value = arguments.required("value");
83 let format = arguments.required("format");
84 let timezone = arguments.optional("timezone");
85
86 Ok(ParseTimestampFn {
87 value,
88 format,
89 timezone,
90 }
91 .as_expr())
92 }
93
94 fn parameters(&self) -> &'static [Parameter] {
95 const PARAMETERS: &[Parameter] = &[
96 Parameter::required("value", kind::BYTES | kind::TIMESTAMP, "The text of the timestamp."),
97 Parameter::required("format", kind::BYTES, "The [strptime](https://docs.rs/chrono/latest/chrono/format/strftime/index.html#specifiers) format."),
98 Parameter::optional("timezone", kind::BYTES, "The [TZ database](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) format. By default, this function parses the timestamp by global [`timezone` option](/docs/reference/configuration//global-options#timezone).
99This argument overwrites the setting and is useful for parsing timestamps without a specified timezone, such as `16/10/2019 12:00:00`."),
100 ];
101 PARAMETERS
102 }
103}
104
105#[derive(Debug, Clone)]
106struct ParseTimestampFn {
107 value: Box<dyn Expression>,
108 format: Box<dyn Expression>,
109 timezone: Option<Box<dyn Expression>>,
110}
111
112impl FunctionExpression for ParseTimestampFn {
113 fn resolve(&self, ctx: &mut Context) -> Resolved {
114 let value = self.value.resolve(ctx)?;
115 let format = self.format.resolve(ctx)?;
116 let tz = self
117 .timezone
118 .as_ref()
119 .map(|tz| tz.resolve(ctx))
120 .transpose()?;
121 parse_timestamp(value, &format, tz, ctx)
122 }
123
124 fn type_def(&self, _: &state::TypeState) -> TypeDef {
125 TypeDef::timestamp().fallible()
126 }
127}
128
129#[cfg(test)]
130mod tests {
131 use super::*;
132 use crate::value;
133 use chrono::{DateTime, Utc};
134
135 test_function![
136 parse_timestamp => ParseTimestamp;
137
138 parse_timestamp {
139 args: func_args![
140 value: DateTime::parse_from_rfc2822("Wed, 16 Oct 2019 12:00:00 +0000")
141 .unwrap()
142 .with_timezone(&Utc),
143 format:"%d/%m/%Y:%H:%M:%S %z"
144 ],
145 want: Ok(value!(
146 DateTime::parse_from_rfc2822("Wed, 16 Oct 2019 12:00:00 +0000")
147 .unwrap()
148 .with_timezone(&Utc)
149 )),
150 tdef: TypeDef::timestamp().fallible(),
151 tz: TimeZone::default(),
152 }
153
154 parse_text {
155 args: func_args![
156 value: "16/10/2019:12:00:00 +0000",
157 format: "%d/%m/%Y:%H:%M:%S %z"
158 ],
159 want: Ok(value!(
160 DateTime::parse_from_rfc2822("Wed, 16 Oct 2019 12:00:00 +0000")
161 .unwrap()
162 .with_timezone(&Utc)
163 )),
164 tdef: TypeDef::timestamp().fallible(),
165 tz: TimeZone::default(),
166 }
167
168 parse_text_with_tz {
169 args: func_args![
170 value: "16/10/2019:12:00:00",
171 format: "%d/%m/%Y:%H:%M:%S"
172 ],
173 want: Ok(value!(
174 DateTime::parse_from_rfc2822("Wed, 16 Oct 2019 10:00:00 +0000")
175 .unwrap()
176 .with_timezone(&Utc)
177 )),
178 tdef: TypeDef::timestamp().fallible(),
179 tz: TimeZone::Named(chrono_tz::Europe::Paris),
180 }
181
182 parse_text_with_timezone_args_no_dst {
184 args: func_args![
185 value: "31/12/2019:12:00:00",
186 format: "%d/%m/%Y:%H:%M:%S",
187 timezone: "Europe/Paris"
188 ],
189 want: Ok(value!(
190 DateTime::parse_from_rfc2822("Tue, 31 Dec 2019 11:00:00 +0000")
191 .unwrap()
192 .with_timezone(&Utc)
193 )),
194 tdef: TypeDef::timestamp().fallible(),
195 tz: TimeZone::default(),
196 }
197
198 parse_text_with_favor_timezone_args_than_tz_no_dst {
199 args: func_args![
200 value: "31/12/2019:12:00:00",
201 format: "%d/%m/%Y:%H:%M:%S",
202 timezone: "Europe/Paris"
203 ],
204 want: Ok(value!(
205 DateTime::parse_from_rfc2822("Tue, 31 Dec 2019 11:00:00 +0000")
206 .unwrap()
207 .with_timezone(&Utc)
208 )),
209 tdef: TypeDef::timestamp().fallible(),
210 tz: TimeZone::Named(chrono_tz::Europe::London),
211 }
212
213 err_timezone_args {
214 args: func_args![
215 value: "16/10/2019:12:00:00",
216 format: "%d/%m/%Y:%H:%M:%S",
217 timezone: "Europe/Pariss"
218 ],
219 want: Err("unable to parse timezone: Europe/Pariss"),
220 tdef: TypeDef::timestamp().fallible(),
221 tz: TimeZone::default(),
222 }
223
224 err_value_null {
225 args: func_args![
226 value: value!(null),
227 format: "%d/%m/%Y:%H:%M:%S",
228 ],
229 want: Err("unable to convert null value to timestamp"),
230 tdef: TypeDef::timestamp().fallible(),
231 tz: TimeZone::default(),
232 }
233 ];
234}