Skip to main content

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}