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}