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}