Skip to main content

rust_query/value/
jiff_operations.rs

1use jiff::fmt::temporal;
2use sea_query::ExprTrait;
3
4use crate::{Expr, IntoExpr};
5
6fn const_str(val: &str) -> sea_query::Value {
7    sea_query::Value::String(Some(val.to_owned()))
8}
9fn concat(a: impl Into<sea_query::Expr>, b: impl Into<sea_query::Expr>) -> sea_query::Expr {
10    a.into().binary(sea_query::BinOper::Custom("||"), b)
11}
12
13impl<'column, S> Expr<'column, S, jiff::Timestamp> {
14    /// Number of whole seconds since the unix epoch.
15    ///
16    /// Fractional seconds are truncated (matching jiff behaviour).
17    /// Before 1970 seconds are rounded up and after 1970 seconds are rounded down.
18    ///
19    /// ```
20    /// # use rust_query::IntoExpr;
21    /// # rust_query::private::doctest::get_txn(|txn| {
22    /// use jiff::Timestamp;
23    /// assert_eq!(txn.query_one(Timestamp::from_millisecond(123713).unwrap().into_expr().to_second()), 123);
24    /// assert_eq!(txn.query_one(Timestamp::from_millisecond(-123713).unwrap().into_expr().to_second()), -123);
25    /// # });
26    /// ```
27    pub fn to_second(&self) -> Expr<'column, S, i64> {
28        let this = self.inner.clone();
29        Expr::adhoc(move |b| {
30            sea_query::Func::cust("timestamp_to_second")
31                .arg(this.build_expr(b))
32                .into()
33        })
34    }
35
36    /// The fractional component of the timestamp in seconds.
37    ///
38    /// Negative for timestamps before 1970 (matching jiff behaviour).
39    ///
40    /// ```
41    /// # use rust_query::IntoExpr;
42    /// # rust_query::private::doctest::get_txn(|txn| {
43    /// use jiff::Timestamp;
44    /// assert_eq!(txn.query_one(Timestamp::from_millisecond(123713).unwrap().into_expr().subsec_nanosecond()), 713000000);
45    /// assert_eq!(txn.query_one(Timestamp::from_millisecond(-123713).unwrap().into_expr().subsec_nanosecond()), -713000000);
46    /// # });
47    /// ```
48    pub fn subsec_nanosecond(&self) -> Expr<'column, S, i64> {
49        let this = self.inner.clone();
50        Expr::adhoc(move |b| {
51            sea_query::Func::cust("timestamp_subsec_nanosecond")
52                .arg(this.build_expr(b))
53                .into()
54        })
55    }
56
57    /// New timestamp from seconds since the unix epoch.
58    ///
59    /// ```
60    /// # use rust_query::IntoExpr;
61    /// # rust_query::private::doctest::get_txn(|txn| {
62    /// use jiff::Timestamp;
63    /// use rust_query::Expr;
64    /// assert_eq!(txn.query_one(Expr::from_second(4322)), Timestamp::from_second(4322).unwrap());
65    /// assert_eq!(txn.query_one(Expr::from_second(-4322)), Timestamp::from_second(-4322).unwrap());
66    /// # });
67    /// ```
68    pub fn from_second(val: impl IntoExpr<'column, S, Typ = i64>) -> Self {
69        let this = val.into_expr().inner;
70        Expr::adhoc(move |b| {
71            sea_query::Func::cust("datetime")
72                .arg(this.build_expr(b))
73                .arg(const_str("unixepoch"))
74                .into()
75        })
76    }
77
78    /// Add a number of nanoseconds to a timestamp
79    ///
80    /// ```
81    /// # use rust_query::IntoExpr;
82    /// # rust_query::private::doctest::get_txn(|txn| {
83    /// use jiff::Timestamp;
84    /// use rust_query::Expr;
85    /// let ts = Timestamp::from_millisecond(123713).unwrap();
86    /// assert_eq!(txn.query_one(Expr::from_second(ts.as_second()).add_nanosecond(ts.subsec_nanosecond() as i64)), ts);
87    /// let ts = Timestamp::from_millisecond(-123713).unwrap();
88    /// assert_eq!(txn.query_one(Expr::from_second(ts.as_second()).add_nanosecond(ts.subsec_nanosecond() as i64)), ts);
89    /// # });
90    /// ```
91    pub fn add_nanosecond(&self, nanos: impl IntoExpr<'column, S, Typ = i64>) -> Self {
92        let this = self.inner.clone();
93        let nanos = nanos.into_expr().inner;
94        Expr::adhoc(move |b| {
95            sea_query::Func::cust("timestamp_add_nanosecond")
96                .arg(this.build_expr(b))
97                .arg(nanos.build_expr(b))
98                .into()
99        })
100    }
101
102    /// Convert a timestamp to a date using the specified timezone.
103    ///
104    /// ```
105    /// # use rust_query::IntoExpr;
106    /// # use std::str::FromStr;
107    /// # rust_query::private::doctest::get_txn(|txn| {
108    /// use jiff::Timestamp;
109    /// use jiff::tz::TimeZone;
110    /// use jiff::civil::Date;
111    /// let ts = Timestamp::from_second(123713).unwrap();
112    /// let tz = TimeZone::get("Europe/Amsterdam").unwrap();
113    /// assert_eq!(txn.query_one(ts.into_expr().to_date_in_tz(&tz)), Date::from_str("1970-01-02").unwrap());
114    /// # });
115    /// ```
116    pub fn to_date_in_tz(&self, tz: &jiff::tz::TimeZone) -> Expr<'column, S, jiff::civil::Date> {
117        static PRINTER: temporal::DateTimePrinter = temporal::DateTimePrinter::new();
118        let time_zone = PRINTER.time_zone_to_string(tz).unwrap();
119        let this = self.inner.clone();
120
121        Expr::adhoc(move |b| {
122            sea_query::Func::cust("timestamp_to_date")
123                .arg(this.build_expr(b))
124                .arg(time_zone.clone())
125                .into()
126        })
127    }
128}
129
130impl<'column, S> Expr<'column, S, jiff::civil::Date> {
131    /// The year in the range `0..=9999`.
132    ///
133    /// ```
134    /// # use rust_query::IntoExpr;
135    /// # rust_query::private::doctest::get_txn(|txn| {
136    /// use jiff::civil::date;
137    /// assert_eq!(txn.query_one(date(2300, 3, 6).into_expr().year()), 2300);
138    /// # });
139    /// ```
140    pub fn year(&self) -> Expr<'column, S, i64> {
141        let this = self.inner.clone();
142        Expr::adhoc(move |b| {
143            sea_query::Func::cust("strftime")
144                .arg(const_str("%Y"))
145                .arg(this.build_expr(b))
146                .cast_as("INTEGER")
147        })
148    }
149
150    /// The month of the year in the range `1..=12`.
151    ///
152    /// ```
153    /// # use rust_query::IntoExpr;
154    /// # rust_query::private::doctest::get_txn(|txn| {
155    /// use jiff::civil::date;
156    /// assert_eq!(txn.query_one(date(2300, 3, 6).into_expr().month()), 3);
157    /// # });
158    /// ```
159    pub fn month(&self) -> Expr<'column, S, i64> {
160        let this = self.inner.clone();
161        Expr::adhoc(move |b| {
162            sea_query::Func::cust("strftime")
163                .arg(const_str("%m"))
164                .arg(this.build_expr(b))
165                .cast_as("INTEGER")
166        })
167    }
168
169    /// The day of the month in the range `1..=31`.
170    ///
171    /// ```
172    /// # use rust_query::IntoExpr;
173    /// # rust_query::private::doctest::get_txn(|txn| {
174    /// use jiff::civil::date;
175    /// assert_eq!(txn.query_one(date(2300, 3, 6).into_expr().day()), 6);
176    /// # });
177    /// ```
178    pub fn day(&self) -> Expr<'column, S, i64> {
179        let this = self.inner.clone();
180        Expr::adhoc(move |b| {
181            sea_query::Func::cust("strftime")
182                .arg(const_str("%d"))
183                .arg(this.build_expr(b))
184                .cast_as("INTEGER")
185        })
186    }
187
188    /// Give the first day of the month.
189    ///
190    /// - `idx == 0` the first day of this month.
191    /// - `idx == 1` the first day of next month.
192    /// - `idx == -1` the first day of previous month.
193    /// - etc.
194    ///
195    /// ```
196    /// # use rust_query::IntoExpr;
197    /// # rust_query::private::doctest::get_txn(|txn| {
198    /// use jiff::civil::date;
199    /// assert_eq!(txn.query_one(date(2300, 3, 6).into_expr().first_of_month(0)), date(2300, 3, 1));
200    /// assert_eq!(txn.query_one(date(2300, 3, 6).into_expr().first_of_month(1)), date(2300, 4, 1));
201    /// assert_eq!(txn.query_one(date(2300, 3, 6).into_expr().first_of_month(12)), date(2301, 3, 1));
202    /// assert_eq!(txn.query_one(date(2300, 3, 6).into_expr().first_of_month(-1)), date(2300, 2, 1));
203    /// # });
204    /// ```
205    pub fn first_of_month(&self, idx: impl IntoExpr<'column, S, Typ = i64>) -> Self {
206        let this = self.inner.clone();
207        let idx = idx.into_expr().inner;
208
209        Expr::adhoc(move |b| {
210            sea_query::Func::cust("date")
211                .arg(this.build_expr(b))
212                .arg(const_str("start of month"))
213                .arg(concat(idx.build_expr(b), const_str(" month")))
214                .into()
215        })
216    }
217
218    /// Add a number of days to this date.
219    ///
220    /// ```
221    /// # use rust_query::IntoExpr;
222    /// # rust_query::private::doctest::get_txn(|txn| {
223    /// use jiff::civil::date;
224    /// assert_eq!(txn.query_one(date(2300, 3, 6).into_expr().add_day(30)), date(2300, 4, 5));
225    /// # });
226    /// ```
227    pub fn add_day(&self, days: impl IntoExpr<'column, S, Typ = i64>) -> Self {
228        let this = self.inner.clone();
229        let days = days.into_expr().inner;
230        Expr::adhoc(move |b| {
231            sea_query::Func::cust("date")
232                .arg(this.build_expr(b))
233                .arg(concat(days.build_expr(b), const_str(" day")))
234                .into()
235        })
236    }
237}