vrl/stdlib/
from_unix_timestamp.rs

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}