oracle/sql_type/
chrono.rs

1// Rust-oracle - Rust binding for Oracle database
2//
3// URL: https://github.com/kubo/rust-oracle
4//
5//-----------------------------------------------------------------------------
6// Copyright (c) 2017-2018 Kubo Takehiro <kubo@jiubao.org>. All rights reserved.
7// This program is free software: you can modify it and/or redistribute it
8// under the terms of:
9//
10// (i)  the Universal Permissive License v 1.0 or at your option, any
11//      later version (http://oss.oracle.com/licenses/upl); and/or
12//
13// (ii) the Apache License v 2.0. (http://www.apache.org/licenses/LICENSE-2.0)
14//-----------------------------------------------------------------------------
15
16use chrono::prelude::*;
17
18use crate::sql_type::FromSql;
19use crate::sql_type::IntervalDS;
20use crate::sql_type::OracleType;
21use crate::sql_type::Timestamp;
22use crate::sql_type::ToSql;
23use crate::sql_type::ToSqlNull;
24use crate::Connection;
25use crate::Error;
26use crate::Result;
27use crate::SqlValue;
28use chrono::naive::NaiveDate;
29use chrono::naive::NaiveDateTime;
30use chrono::offset::LocalResult;
31use chrono::Duration;
32
33fn fixed_offset_from_sql(ts: &Timestamp) -> Result<FixedOffset> {
34    FixedOffset::east_opt(ts.tz_offset())
35        .ok_or_else(|| Error::out_of_range(format!("invalid time zone offset: {}", ts.tz_offset())))
36}
37
38//
39// chrono::DateTime<Utc>
40// chrono::DateTime<Local>
41// chrono::DateTime<FixedOffset>
42//
43
44fn datetime_from_sql<Tz>(tz: &Tz, ts: &Timestamp) -> Result<DateTime<Tz>>
45where
46    Tz: TimeZone,
47{
48    date_from_sql(tz, ts)?
49        .and_hms_nano_opt(ts.hour(), ts.minute(), ts.second(), ts.nanosecond())
50        .ok_or_else(|| {
51            Error::out_of_range(format!(
52                "invalid year-month-day: {}-{}-{} {}:{}:{}.{:09}",
53                ts.year(),
54                ts.month(),
55                ts.day(),
56                ts.hour(),
57                ts.minute(),
58                ts.second(),
59                ts.nanosecond()
60            ))
61        })
62}
63
64impl FromSql for DateTime<Utc> {
65    fn from_sql(val: &SqlValue) -> Result<DateTime<Utc>> {
66        let ts = val.to_timestamp()?;
67        datetime_from_sql(&Utc, &ts)
68    }
69}
70
71impl FromSql for DateTime<Local> {
72    fn from_sql(val: &SqlValue) -> Result<DateTime<Local>> {
73        let ts = val.to_timestamp()?;
74        datetime_from_sql(&Local, &ts)
75    }
76}
77
78impl FromSql for DateTime<FixedOffset> {
79    fn from_sql(val: &SqlValue) -> Result<DateTime<FixedOffset>> {
80        let ts = val.to_timestamp()?;
81        datetime_from_sql(&fixed_offset_from_sql(&ts)?, &ts)
82    }
83}
84
85impl<Tz> ToSqlNull for DateTime<Tz>
86where
87    Tz: TimeZone,
88{
89    fn oratype_for_null(_conn: &Connection) -> Result<OracleType> {
90        Ok(OracleType::TimestampTZ(9))
91    }
92}
93
94impl<Tz> ToSql for DateTime<Tz>
95where
96    Tz: TimeZone,
97{
98    fn oratype(&self, _conn: &Connection) -> Result<OracleType> {
99        Ok(OracleType::TimestampTZ(9))
100    }
101
102    fn to_sql(&self, val: &mut SqlValue) -> Result<()> {
103        let ts = Timestamp::new(
104            self.year(),
105            self.month(),
106            self.day(),
107            self.hour(),
108            self.minute(),
109            self.second(),
110            self.nanosecond(),
111        )?;
112        let ts = ts.and_tz_offset(self.offset().fix().local_minus_utc())?;
113        val.set_timestamp(&ts)
114    }
115}
116
117//
118// chrono::Date<Utc>
119// chrono::Date<Local>
120// chrono::Date<FixedOffset>
121//
122// chrono::Date has been deprecated since chrono 0.4.23.
123// However it cannot be removed without a breaking change.
124// So `#[allow(deprecated)]` was added for each trait implementation
125// to suppress warnings.
126
127#[allow(deprecated)]
128fn date_from_sql<Tz>(tz: &Tz, ts: &Timestamp) -> Result<Date<Tz>>
129where
130    Tz: TimeZone,
131{
132    match tz.ymd_opt(ts.year(), ts.month(), ts.day()) {
133        LocalResult::Single(date) => Ok(date),
134        _ => Err(Error::out_of_range(format!(
135            "invalid month and/or day: {}-{}-{}",
136            ts.year(),
137            ts.month(),
138            ts.day()
139        ))),
140    }
141}
142
143#[allow(deprecated)]
144impl FromSql for Date<Utc> {
145    fn from_sql(val: &SqlValue) -> Result<Date<Utc>> {
146        let ts = val.to_timestamp()?;
147        date_from_sql(&Utc, &ts)
148    }
149}
150
151#[allow(deprecated)]
152impl FromSql for Date<Local> {
153    fn from_sql(val: &SqlValue) -> Result<Date<Local>> {
154        let ts = val.to_timestamp()?;
155        date_from_sql(&Local, &ts)
156    }
157}
158
159#[allow(deprecated)]
160impl FromSql for Date<FixedOffset> {
161    fn from_sql(val: &SqlValue) -> Result<Date<FixedOffset>> {
162        let ts = val.to_timestamp()?;
163        date_from_sql(&fixed_offset_from_sql(&ts)?, &ts)
164    }
165}
166
167#[allow(deprecated)]
168impl<Tz> ToSqlNull for Date<Tz>
169where
170    Tz: TimeZone,
171{
172    fn oratype_for_null(_conn: &Connection) -> Result<OracleType> {
173        Ok(OracleType::TimestampTZ(0))
174    }
175}
176
177#[allow(deprecated)]
178impl<Tz> ToSql for Date<Tz>
179where
180    Tz: TimeZone,
181{
182    fn oratype(&self, _conn: &Connection) -> Result<OracleType> {
183        Ok(OracleType::TimestampTZ(0))
184    }
185
186    fn to_sql(&self, val: &mut SqlValue) -> Result<()> {
187        let ts = Timestamp::new(self.year(), self.month(), self.day(), 0, 0, 0, 0)?;
188        let ts = ts.and_tz_offset(self.offset().fix().local_minus_utc())?;
189        val.set_timestamp(&ts)
190    }
191}
192
193//
194// chrono::naive::NaiveDateTime
195//
196
197fn naive_date_time_from_sql(ts: &Timestamp) -> Result<NaiveDateTime> {
198    naive_date_from_sql(ts)?
199        .and_hms_nano_opt(ts.hour(), ts.minute(), ts.second(), ts.nanosecond())
200        .ok_or_else(|| {
201            Error::out_of_range(format!(
202                "invalid year-month-day: {}-{}-{} {}:{}:{}.{:09}",
203                ts.year(),
204                ts.month(),
205                ts.day(),
206                ts.hour(),
207                ts.minute(),
208                ts.second(),
209                ts.nanosecond()
210            ))
211        })
212}
213
214impl FromSql for NaiveDateTime {
215    fn from_sql(val: &SqlValue) -> Result<NaiveDateTime> {
216        let ts = val.to_timestamp()?;
217        naive_date_time_from_sql(&ts)
218    }
219}
220
221impl ToSqlNull for NaiveDateTime {
222    fn oratype_for_null(_conn: &Connection) -> Result<OracleType> {
223        Ok(OracleType::Timestamp(9))
224    }
225}
226
227impl ToSql for NaiveDateTime {
228    fn oratype(&self, _conn: &Connection) -> Result<OracleType> {
229        Ok(OracleType::Timestamp(9))
230    }
231
232    fn to_sql(&self, val: &mut SqlValue) -> Result<()> {
233        let ts = Timestamp::new(
234            self.year(),
235            self.month(),
236            self.day(),
237            self.hour(),
238            self.minute(),
239            self.second(),
240            self.nanosecond(),
241        )?;
242        val.set_timestamp(&ts)
243    }
244}
245
246//
247// chrono::naive::NaiveDate
248//
249
250fn naive_date_from_sql(ts: &Timestamp) -> Result<NaiveDate> {
251    NaiveDate::from_ymd_opt(ts.year(), ts.month(), ts.day()).ok_or_else(|| {
252        Error::out_of_range(format!(
253            "invalid year-month-day: {}-{}-{}",
254            ts.year(),
255            ts.month(),
256            ts.day()
257        ))
258    })
259}
260
261impl FromSql for NaiveDate {
262    fn from_sql(val: &SqlValue) -> Result<NaiveDate> {
263        let ts = val.to_timestamp()?;
264        naive_date_from_sql(&ts)
265    }
266}
267
268impl ToSqlNull for NaiveDate {
269    fn oratype_for_null(_conn: &Connection) -> Result<OracleType> {
270        Ok(OracleType::Timestamp(0))
271    }
272}
273
274impl ToSql for NaiveDate {
275    fn oratype(&self, _conn: &Connection) -> Result<OracleType> {
276        Ok(OracleType::Timestamp(0))
277    }
278
279    fn to_sql(&self, val: &mut SqlValue) -> Result<()> {
280        let ts = Timestamp::new(self.year(), self.month(), self.day(), 0, 0, 0, 0)?;
281        val.set_timestamp(&ts)
282    }
283}
284
285//
286// chrono::Duration
287//
288
289impl FromSql for Duration {
290    fn from_sql(val: &SqlValue) -> Result<Duration> {
291        let err = |it: IntervalDS| {
292            Error::out_of_range(format!(
293                "unable to convert interval day to second {} to chrono::Duration",
294                it
295            ))
296        };
297        let it = val.to_interval_ds()?;
298        let d = Duration::milliseconds(0);
299        let d = d
300            .checked_add(&Duration::days(it.days() as i64))
301            .ok_or_else(|| err(it))?;
302        let d = d
303            .checked_add(&Duration::hours(it.hours() as i64))
304            .ok_or_else(|| err(it))?;
305        let d = d
306            .checked_add(&Duration::minutes(it.minutes() as i64))
307            .ok_or_else(|| err(it))?;
308        let d = d
309            .checked_add(&Duration::seconds(it.seconds() as i64))
310            .ok_or_else(|| err(it))?;
311        let d = d
312            .checked_add(&Duration::nanoseconds(it.nanoseconds() as i64))
313            .ok_or_else(|| err(it))?;
314        Ok(d)
315    }
316}
317
318impl ToSqlNull for Duration {
319    fn oratype_for_null(_conn: &Connection) -> Result<OracleType> {
320        Ok(OracleType::IntervalDS(9, 9))
321    }
322}
323
324impl ToSql for Duration {
325    fn oratype(&self, _conn: &Connection) -> Result<OracleType> {
326        Ok(OracleType::IntervalDS(9, 9))
327    }
328
329    fn to_sql(&self, val: &mut SqlValue) -> Result<()> {
330        let secs = self.num_seconds();
331        let nsecs = (*self - Duration::seconds(secs)).num_nanoseconds().unwrap();
332        let days = secs / (24 * 60 * 60);
333        let secs = secs % (24 * 60 * 60);
334        let hours = secs / (60 * 60);
335        let secs = secs % (60 * 60);
336        let minutes = secs / 60;
337        let secs = secs % 60;
338        if days.abs() >= 1000000000 {
339            return Err(Error::out_of_range(format!("too large days: {}", self)));
340        }
341        let it = IntervalDS::new(
342            days as i32,
343            hours as i32,
344            minutes as i32,
345            secs as i32,
346            nsecs as i32,
347        )?;
348        val.set_interval_ds(&it)
349    }
350}