vrl/stdlib/
to_unix_timestamp.rs

1use crate::compiler::function::EnumVariant;
2use crate::compiler::prelude::*;
3use std::str::FromStr;
4use std::sync::LazyLock;
5
6static DEFAULT_UNIT: LazyLock<Value> = LazyLock::new(|| Value::Bytes(Bytes::from("seconds")));
7
8static UNIT_ENUM: &[EnumVariant] = &[
9    EnumVariant {
10        value: "seconds",
11        description: "Express Unix time in seconds",
12    },
13    EnumVariant {
14        value: "milliseconds",
15        description: "Express Unix time in milliseconds",
16    },
17    EnumVariant {
18        value: "nanoseconds",
19        description: "Express Unix time in nanoseconds",
20    },
21];
22
23static PARAMETERS: LazyLock<Vec<Parameter>> = LazyLock::new(|| {
24    vec![
25        Parameter::required(
26            "value",
27            kind::TIMESTAMP,
28            "The timestamp to convert into a Unix timestamp.",
29        ),
30        Parameter::optional("unit", kind::BYTES, "The time unit.")
31            .default(&DEFAULT_UNIT)
32            .enum_variants(UNIT_ENUM),
33    ]
34});
35
36fn to_unix_timestamp(value: Value, unit: Unit) -> Resolved {
37    let ts = value.try_timestamp()?;
38    let time = match unit {
39        Unit::Seconds => ts.timestamp(),
40        Unit::Milliseconds => ts.timestamp_millis(),
41        Unit::Microseconds => ts.timestamp_micros(),
42        Unit::Nanoseconds => match ts.timestamp_nanos_opt() {
43            None => return Err(ValueError::OutOfRange(Kind::timestamp()).into()),
44            Some(nanos) => nanos,
45        },
46    };
47    Ok(time.into())
48}
49
50#[derive(Clone, Copy, Debug)]
51pub struct ToUnixTimestamp;
52
53impl Function for ToUnixTimestamp {
54    fn identifier(&self) -> &'static str {
55        "to_unix_timestamp"
56    }
57
58    fn usage(&self) -> &'static str {
59        indoc! {"
60            Converts the `value` timestamp into a [Unix timestamp](https://en.wikipedia.org/wiki/Unix_time).
61
62            Returns the number of seconds since the Unix epoch by default. To return the number in milliseconds or nanoseconds, set the `unit` argument to `milliseconds` or `nanoseconds`.
63        "}
64    }
65
66    fn category(&self) -> &'static str {
67        Category::Convert.as_ref()
68    }
69
70    fn internal_failure_reasons(&self) -> &'static [&'static str] {
71        &[
72            "`value` cannot be represented in nanoseconds. Result is too large or too small for a 64 bit integer.",
73        ]
74    }
75
76    fn return_kind(&self) -> u16 {
77        kind::INTEGER
78    }
79
80    fn examples(&self) -> &'static [Example] {
81        &[
82            example! {
83                title: "Convert to a Unix timestamp (seconds)",
84                source: "to_unix_timestamp(t'2021-01-01T00:00:00+00:00')",
85                result: Ok("1609459200"),
86            },
87            example! {
88                title: "Convert to a Unix timestamp (milliseconds)",
89                source: r#"to_unix_timestamp(t'2021-01-01T00:00:00Z', unit: "milliseconds")"#,
90                result: Ok("1609459200000"),
91            },
92            example! {
93                title: "Convert to a Unix timestamp (microseconds)",
94                source: r#"to_unix_timestamp(t'2021-01-01T00:00:00Z', unit: "microseconds")"#,
95                result: Ok("1609459200000000"),
96            },
97            example! {
98                title: "Convert to a Unix timestamp (nanoseconds)",
99                source: r#"to_unix_timestamp(t'2021-01-01T00:00:00Z', unit: "nanoseconds")"#,
100                result: Ok("1609459200000000000"),
101            },
102        ]
103    }
104
105    fn parameters(&self) -> &'static [Parameter] {
106        PARAMETERS.as_slice()
107    }
108
109    fn compile(
110        &self,
111        state: &state::TypeState,
112        _ctx: &mut FunctionCompileContext,
113        arguments: ArgumentList,
114    ) -> Compiled {
115        let value = arguments.required("value");
116
117        let unit = Unit::from_str(
118            &arguments
119                .optional_enum("unit", Unit::all_value().as_slice(), state)?
120                .unwrap_or_else(|| DEFAULT_UNIT.clone())
121                .try_bytes_utf8_lossy()
122                .expect("unit not bytes"),
123        )
124        .expect("validated enum");
125
126        Ok(ToUnixTimestampFn { value, unit }.as_expr())
127    }
128}
129
130#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
131enum Unit {
132    #[default]
133    Seconds,
134    Milliseconds,
135    Microseconds,
136    Nanoseconds,
137}
138
139impl Unit {
140    fn all_value() -> Vec<Value> {
141        use Unit::{Microseconds, Milliseconds, Nanoseconds, Seconds};
142
143        vec![Seconds, Milliseconds, Microseconds, Nanoseconds]
144            .into_iter()
145            .map(|u| u.as_str().into())
146            .collect::<Vec<_>>()
147    }
148
149    const fn as_str(self) -> &'static str {
150        use Unit::{Microseconds, Milliseconds, Nanoseconds, Seconds};
151
152        match self {
153            Seconds => "seconds",
154            Milliseconds => "milliseconds",
155            Microseconds => "microseconds",
156            Nanoseconds => "nanoseconds",
157        }
158    }
159}
160
161impl FromStr for Unit {
162    type Err = &'static str;
163
164    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
165        use Unit::{Microseconds, Milliseconds, Nanoseconds, Seconds};
166
167        match s {
168            "seconds" => Ok(Seconds),
169            "milliseconds" => Ok(Milliseconds),
170            "microseconds" => Ok(Microseconds),
171            "nanoseconds" => Ok(Nanoseconds),
172            _ => Err("unit not recognized"),
173        }
174    }
175}
176
177#[derive(Debug, Clone)]
178struct ToUnixTimestampFn {
179    value: Box<dyn Expression>,
180    unit: Unit,
181}
182
183impl FunctionExpression for ToUnixTimestampFn {
184    fn resolve(&self, ctx: &mut Context) -> Resolved {
185        let value = self.value.resolve(ctx)?;
186        let unit = self.unit;
187
188        to_unix_timestamp(value, unit)
189    }
190
191    fn type_def(&self, _: &state::TypeState) -> TypeDef {
192        TypeDef::integer().infallible()
193    }
194}
195
196#[cfg(test)]
197mod test {
198    use chrono::{TimeZone, Utc};
199
200    use super::*;
201
202    test_function![
203        to_unix_timestamp => ToUnixTimestamp;
204
205        seconds {
206            args: func_args![value: Utc.with_ymd_and_hms(2021, 1, 1, 0, 0, 0).unwrap(),
207                             unit: "seconds"
208            ],
209            want: Ok(1_609_459_200_i64),
210            tdef: TypeDef::integer().infallible(),
211        }
212
213        milliseconds {
214            args: func_args![value: Utc.with_ymd_and_hms(2021, 1, 1, 0, 0, 0).unwrap(),
215                             unit: "milliseconds"
216            ],
217            want: Ok(1_609_459_200_000_i64),
218            tdef: TypeDef::integer().infallible(),
219        }
220
221        microseconds {
222            args: func_args![value: Utc.with_ymd_and_hms(2021, 1, 1, 0, 0, 0).unwrap(),
223                             unit: "microseconds"
224            ],
225            want: Ok(1_609_459_200_000_000_i64),
226            tdef: TypeDef::integer().infallible(),
227        }
228
229        nanoseconds {
230             args: func_args![value: Utc.with_ymd_and_hms(2021, 1, 1, 0, 0, 0).unwrap(),
231                              unit: "nanoseconds"
232             ],
233             want: Ok(1_609_459_200_000_000_000_i64),
234             tdef: TypeDef::integer().infallible(),
235         }
236         out_of_range {
237             args: func_args![value: Utc.with_ymd_and_hms(0, 1, 1, 0, 0, 0).unwrap(),
238                              unit: "nanoseconds"
239             ],
240             want: Err("can't convert out of range timestamp"),
241             tdef: TypeDef::integer().infallible(),
242         }
243    ];
244}