1use crate::compiler::function::EnumVariant;
2use crate::compiler::prelude::*;
3use chrono::{TimeZone as _, Utc};
4use std::str::FromStr;
5use std::sync::LazyLock;
6
7static DEFAULT_UNIT: LazyLock<Value> = LazyLock::new(|| Value::Bytes(Bytes::from("seconds")));
8
9static UNIT_ENUM: &[EnumVariant] = &[
10 EnumVariant {
11 value: "seconds",
12 description: "Express Unix time in seconds",
13 },
14 EnumVariant {
15 value: "milliseconds",
16 description: "Express Unix time in milliseconds",
17 },
18 EnumVariant {
19 value: "nanoseconds",
20 description: "Express Unix time in nanoseconds",
21 },
22 EnumVariant {
23 value: "microseconds",
24 description: "Express Unix time in microseconds",
25 },
26];
27
28static PARAMETERS: LazyLock<Vec<Parameter>> = LazyLock::new(|| {
29 vec![
30 Parameter::required("value", kind::INTEGER, "The Unix timestamp to convert."),
31 Parameter::optional("unit", kind::BYTES, "The time unit.")
32 .default(&DEFAULT_UNIT)
33 .enum_variants(UNIT_ENUM),
34 ]
35});
36
37fn from_unix_timestamp(value: Value, unit: Unit) -> Resolved {
38 use Value::Integer;
39
40 let value = match value {
41 Integer(v) => match unit {
42 Unit::Seconds => match Utc.timestamp_opt(v, 0).single() {
43 Some(time) => time.into(),
44 None => return Err(format!("unable to coerce {v} into timestamp").into()),
45 },
46 Unit::Milliseconds => match Utc.timestamp_millis_opt(v).single() {
47 Some(time) => time.into(),
48 None => return Err(format!("unable to coerce {v} into timestamp").into()),
49 },
50 Unit::Microseconds => match Utc.timestamp_micros(v).single() {
51 Some(time) => time.into(),
52 None => return Err(format!("unable to coerce {v} into timestamp").into()),
53 },
54 Unit::Nanoseconds => Utc.timestamp_nanos(v).into(),
55 },
56 v => return Err(format!("unable to coerce {} into timestamp", v.kind()).into()),
57 };
58 Ok(value)
59}
60
61#[derive(Clone, Copy, Debug)]
62pub struct FromUnixTimestamp;
63
64impl Function for FromUnixTimestamp {
65 fn identifier(&self) -> &'static str {
66 "from_unix_timestamp"
67 }
68
69 fn usage(&self) -> &'static str {
70 indoc! {"
71 Converts the `value` integer from a [Unix timestamp](https://en.wikipedia.org/wiki/Unix_time) to a VRL `timestamp`.
72
73 Converts from the number of seconds since the Unix epoch by default. To convert from milliseconds or nanoseconds, set the `unit` argument to `milliseconds` or `nanoseconds`.
74 "}
75 }
76
77 fn category(&self) -> &'static str {
78 Category::Convert.as_ref()
79 }
80
81 fn return_kind(&self) -> u16 {
82 kind::TIMESTAMP
83 }
84
85 fn parameters(&self) -> &'static [Parameter] {
86 PARAMETERS.as_slice()
87 }
88
89 fn examples(&self) -> &'static [Example] {
90 &[
91 example! {
92 title: "Convert from a Unix timestamp (seconds)",
93 source: "from_unix_timestamp!(5)",
94 result: Ok("t'1970-01-01T00:00:05Z'"),
95 },
96 example! {
97 title: "Convert from a Unix timestamp (milliseconds)",
98 source: r#"from_unix_timestamp!(5000, unit: "milliseconds")"#,
99 result: Ok("t'1970-01-01T00:00:05Z'"),
100 },
101 example! {
102 title: "Convert from a Unix timestamp (microseconds)",
103 source: r#"from_unix_timestamp!(5000, unit: "microseconds")"#,
104 result: Ok("t'1970-01-01T00:00:00.005Z'"),
105 },
106 example! {
107 title: "Convert from a Unix timestamp (nanoseconds)",
108 source: r#"from_unix_timestamp!(5000, unit: "nanoseconds")"#,
109 result: Ok("t'1970-01-01T00:00:00.000005Z'"),
110 },
111 ]
112 }
113
114 fn compile(
115 &self,
116 state: &state::TypeState,
117 _ctx: &mut FunctionCompileContext,
118 arguments: ArgumentList,
119 ) -> Compiled {
120 let value = arguments.required("value");
121
122 let unit = Unit::from_str(
123 &arguments
124 .optional_enum("unit", Unit::all_value().as_slice(), state)?
125 .unwrap_or_else(|| DEFAULT_UNIT.clone())
126 .try_bytes_utf8_lossy()
127 .expect("unit not bytes"),
128 )
129 .expect("validated enum");
130
131 Ok(FromUnixTimestampFn { value, unit }.as_expr())
132 }
133}
134
135#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
136enum Unit {
137 #[default]
138 Seconds,
139 Milliseconds,
140 Microseconds,
141 Nanoseconds,
142}
143
144impl Unit {
145 fn all_value() -> Vec<Value> {
146 use Unit::{Microseconds, Milliseconds, Nanoseconds, Seconds};
147
148 vec![Seconds, Milliseconds, Microseconds, Nanoseconds]
149 .into_iter()
150 .map(|u| u.as_str().into())
151 .collect::<Vec<_>>()
152 }
153
154 const fn as_str(self) -> &'static str {
155 use Unit::{Microseconds, Milliseconds, Nanoseconds, Seconds};
156
157 match self {
158 Seconds => "seconds",
159 Milliseconds => "milliseconds",
160 Microseconds => "microseconds",
161 Nanoseconds => "nanoseconds",
162 }
163 }
164}
165
166impl FromStr for Unit {
167 type Err = &'static str;
168
169 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
170 use Unit::{Microseconds, Milliseconds, Nanoseconds, Seconds};
171
172 match s {
173 "seconds" => Ok(Seconds),
174 "milliseconds" => Ok(Milliseconds),
175 "microseconds" => Ok(Microseconds),
176 "nanoseconds" => Ok(Nanoseconds),
177 _ => Err("unit not recognized"),
178 }
179 }
180}
181
182#[derive(Debug, Clone)]
183struct FromUnixTimestampFn {
184 value: Box<dyn Expression>,
185 unit: Unit,
186}
187
188impl FunctionExpression for FromUnixTimestampFn {
189 fn resolve(&self, ctx: &mut Context) -> Resolved {
190 let value = self.value.resolve(ctx)?;
191 let unit = self.unit;
192 from_unix_timestamp(value, unit)
193 }
194
195 fn type_def(&self, _state: &state::TypeState) -> TypeDef {
196 TypeDef::timestamp().fallible()
197 }
198}
199
200#[cfg(test)]
201#[allow(overflowing_literals)]
202mod tests {
203 use super::*;
204 use crate::compiler::TimeZone;
205 use crate::compiler::expression::Literal;
206 use crate::value;
207 use regex::Regex;
208 use std::collections::BTreeMap;
209
210 #[test]
211 fn out_of_range_integer() {
212 let mut object: Value = BTreeMap::new().into();
213 let mut runtime_state = state::RuntimeState::default();
214 let tz = TimeZone::default();
215 let mut ctx = Context::new(&mut object, &mut runtime_state, &tz);
216 let f = FromUnixTimestampFn {
217 value: Box::new(Literal::Integer(9_999_999_999_999)),
218 unit: Unit::default(),
219 };
220 let string = f.resolve(&mut ctx).err().unwrap().message();
221 assert_eq!(string, "unable to coerce 9999999999999 into timestamp");
222 }
223
224 test_function![
225 from_unix_timestamp => FromUnixTimestamp;
226
227 integer {
228 args: func_args![value: 1_431_648_000],
229 want: Ok(Utc.with_ymd_and_hms(2015, 5, 15, 0, 0, 0).unwrap()),
230 tdef: TypeDef::timestamp().fallible(),
231 }
232
233 integer_seconds {
234 args: func_args![value: 1_609_459_200_i64, unit: "seconds"],
235 want: Ok(Utc.with_ymd_and_hms(2021, 1, 1, 0, 0, 0).unwrap()),
236 tdef: TypeDef::timestamp().fallible(),
237 }
238
239 integer_milliseconds {
240 args: func_args![value: 1_609_459_200_000_i64, unit: "milliseconds"],
241 want: Ok(Utc.with_ymd_and_hms(2021, 1, 1, 0, 0, 0).unwrap()),
242 tdef: TypeDef::timestamp().fallible(),
243 }
244
245 integer_microseconds {
246 args: func_args![value: 1_609_459_200_000_000_i64, unit: "microseconds"],
247 want: Ok(Utc.with_ymd_and_hms(2021, 1, 1, 0, 0, 0).unwrap()),
248 tdef: TypeDef::timestamp().fallible(),
249 }
250
251 integer_nanoseconds {
252 args: func_args![value: 1_609_459_200_000_000_000_i64, unit: "nanoseconds"],
253 want: Ok(Utc.with_ymd_and_hms(2021, 1, 1, 0, 0, 0).unwrap()),
254 tdef: TypeDef::timestamp().fallible(),
255 }
256
257 float_type_invalid {
258 args: func_args![value: 5.123],
259 want: Err("unable to coerce float into timestamp"),
260 tdef: TypeDef::timestamp().fallible(),
261 }
262
263 float_type_invalid_milliseconds {
264 args: func_args![value: 5.123, unit: "milliseconds"],
265 want: Err("unable to coerce float into timestamp"),
266 tdef: TypeDef::timestamp().fallible(),
267 }
268
269 timestamp_type_invalid {
270 args: func_args![value: Utc.with_ymd_and_hms(2021, 1, 1, 0, 0, 0).unwrap()],
271 want: Err("unable to coerce timestamp into timestamp"),
272 tdef: TypeDef::timestamp().fallible(),
273 }
274
275 boolean_type_invalid {
276 args: func_args![value: true],
277 want: Err("unable to coerce boolean into timestamp"),
278 tdef: TypeDef::timestamp().fallible(),
279 }
280
281 null_type_invalid {
282 args: func_args![value: value!(null)],
283 want: Err("unable to coerce null into timestamp"),
284 tdef: TypeDef::timestamp().fallible(),
285 }
286
287 array_type_invalid {
288 args: func_args![value: value!([])],
289 want: Err("unable to coerce array into timestamp"),
290 tdef: TypeDef::timestamp().fallible(),
291 }
292
293 object_type_invalid {
294 args: func_args![value: value!({})],
295 want: Err("unable to coerce object into timestamp"),
296 tdef: TypeDef::timestamp().fallible(),
297 }
298
299 regex_type_invalid {
300 args: func_args![value: value!(Regex::new(r"\d+").unwrap())],
301 want: Err("unable to coerce regex into timestamp"),
302 tdef: TypeDef::timestamp().fallible(),
303 }
304 ];
305}