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